Node中的回调混乱与采集数据

Callback Confusion and Collecting Data in Node

我是 node 的新手,无法理解这里发生的事情。我的评论在下面,但基本上我真的很难理解为什么 utils.collectData 中的回调在那里(第二个参数)以及它是如何任意添加到 消息数组。我怎么知道将来自然会这样做?我知道 collectData 函数被定义为将回调作为其第二个参数,所以让我们从那里开始...

var utils = require('./utils');

"POST": function(request, response) {
        //collect the data




       utils.collectData(request, function(message) {
            message.objectID = ++objectIDcounter;
            messages.push(message);
            utils.sendResponse(response, {
                objectID: message.objectID
            }, 201)
        })
    }
   // this is in utils.js:
    //why is a callback parameter passed to this function?
    exports.collectData = function(request, callback) {
        var data = ""
        request.on('data', function(chunk) {
            data += chunk;
        })
        request.on('end', function() {
    //why is the callback applied here? 
//Also, we are parsing the data here. why? because it is a buffer or a string here?
                callback(JSON.parse(data));
            })
        }

NodeJs 的主要特点之一是并发(以一种简单的方式)。在 NodeJs 中开始编程的最佳方式是认为所有与外部资源交互的代码都必须遵循以下模式之一:

  • 回调
  • 承诺 (ES2015)
  • Async/Await (ES2016)

这是为什么?

当您以异步方式调用外部资源时(HTTP 服务、文件等)。主进程创建一个内部线程并执行代码,这样做是为了不阻塞主进程。

回调:

是javascript中第一种管理并发的方法,因为函数是first-class,你只传递你想执行的函数,调用时选择主函数.

在您的示例中,您使用的是这种方法。您正在从请求中读取数据并将其存储在局部变量中,当请求启动事件时 'end' 您选择使用数据作为参数调用回调,并且 "POST" 使用该数据来操作消息并发送回复。

将来还有两个选项,今天都可以使用,但 Async/Await 处于 tc39 的第 2 阶段 http://tc39.github.io/ecmascript-asyncawait/

承诺:

是当今最流行的方式,您可以使用原生的 NodeJs 版本 4,或者您可以使用像 Q or BlueBird. This work a litle different, I recommend to read the pouchd post 这样的关于 promise 的库,这里我将展示您使用 promises 编写的代码:

var utils = require('./utils');

"POST": function(request, response) {
        //collect the data
       utils.collectData(request)
            .then(function(message) {
                message.objectID = ++objectIDcounter;
                messages.push(message);
                return utils.sendResponse(response, {
                    objectID: message.objectID
                }, 201);
            }))
            .catch(function(err) {
                //Some error happend with the reques
            });
    }

   // this is in utils.js:
    //why is a callback parameter passed to this function?
    exports.collectData = function(request) {
        return new Promise(function (resolve, reject) {
            var data = ""
            request.on('data', function(chunk) {
                data += chunk;
            });

            request.on('end', function() {
                resolve(JSON.parse(data));
            });

            request.on('error', function(err) {
                reject(err);
            });
        }    
    }

甚至当我从 Java 开始使用 NodeJS 编程时,因为这是设计我们的程序从阻塞 IO 到非阻塞 IO 的重大转变,我也面临着理解上面的代码。

在非阻塞 IO 中,一切都基于事件和回调。在上面调用 collectData 的代码中,nodejs(eventloop) 将等待 on(with callback to collect data) & on(with callback which you have provided to process the completed request) 事件。

在 NodeJS 中很简单,任何阻塞代码(网络调用或访问任何 IO 设备)都将被标记为一个事件,该事件将在 I/O 操作完成后触发。希望这有帮助。

Node.js 中的异步操作遵循简单且一致的模式。掌握这一点将使您的体验更加愉快,并且您的应用程序更快、更稳定。

我认为这里的其他答案有点漏掉了重点。您调用的方法遵循 Node.js 生态系统中普遍存在的异步模式。有多种模式可以简化这一点,但您的首要任务应该是彻底了解正在发生的事情以及它对您的代码产生的影响。这很简单:

asyncFunction(functionArguments, callback);

当您调用任何异步函数时,您的脚本会在该函数的结果待定期间继续执行。由于您无法保证结果何时准备就绪,因此您可以在继续之前向函数提供您对结果的意图。通常,您提供的回调将是一个匿名函数,例如:

getExternalData('http://example.com', function(err, data) {
  console.log('I got the data!');
  // presumably do something with the data
});

回调几乎总是采用两个参数:errdata(或 errresult,或 errwhateverYouAreLookingFor).如果函数中发生错误,它将返回给您的回调,如果函数执行产生任何数据,它也会返回给您的回调。

这种模式的美妙之处在于您永远不会被迫阻止事件循环:无论任务的复杂性如何,您的脚本都会保持愉快的响应。当你的老板在周五午餐后将一个构思不佳的项目放到你的办公桌上,然后早早下班去打高尔夫球时,你实际上是在扮演一个异步角色。

这里的挑战是,当您的回调被调用时,它是原始请求中唯一幸存的 link。其含义是双重的:

  1. 任何用于处理响应的逻辑都必须包含在回调中或可供回调访问;和

  2. try/catch 块对于处理异步函数抛出的任何错误完全没有用。

第一个含义是通常被描述为 "callback hell" 的起源:回调中回调中的深度嵌套回调。您可以通过提前计划、保持函数较小并遵循相同的参数约定来轻松避免这种情况。

第二个含义意味着您的回调必须检查错误。您通常不会看到抛出的错误,因为在这种模式下安全处理抛出的错误的唯一方法是将几乎所有代码放在 try/catch 块中。相反,错误将作为第一个参数传递给回调。如果没有产生错误,第一个参数将为空。

const myCallback = function(err, data) {
  if (!!err) {
    console.log('Well, that was a failure');
    return err;
  }

  console.log('Everything's great!');
}

这最终有点冗长,但让您熟悉这种模式至关重要。一些核心模块具有正常和同步版本的功能,例如fs.readFilefs.readFileSync。不要屈服于使用它们;一旦您习惯了异步,它就非常简单,而且,刚开始,您需要所有的练习。

祝你好运。