【问题标题】:How do I make asynchronous calls in an event handler如何在事件处理程序中进行异步调用
【发布时间】:2016-04-11 18:11:28
【问题描述】:

我正在尝试编写一个自定义 mocha 报告器,该报告器与具有 HTTP API 的第三方集成。

我采用了自定义报告器 (https://github.com/mochajs/mocha/wiki/Third-party-reporters) 给出的基本示例,并尝试在启动事件处理程序中添加对 API 的调用。但是,我对第三方的 http 请求的回调似乎永远不会触发。示例代码如下所示。

我已经测试了请求代码在其他上下文中是否按预期工作,只是似乎在 mocha 报告器的事件处理程序中没有任何反应。

注意 - 请原谅当前的黑客等待回调的结果,我只是想确保在回调有机会触发之前我没有退出,我会在它工作时整理它!

var request = require('request');

module.exports = function (runner) {
    var passes = 0;
    var failures = 0;

    runner.on('start', function() {
        var callbackFired = false;
        request('http://www.testapi.com', function (error, response, body) {
            callbackFired = true;
        });
        while(!callbackFired){
            console.log('waiting...');
        }
    });

    runner.on('pass', function(test){
        passes++;
        console.log('pass: %s', test.fullTitle());
    });

    runner.on('fail', function(test, err){
        failures++;
        console.log('fail: %s -- error: %s', test.fullTitle(), err.message);
    });

    runner.on('end', function(){
        console.log('end: %d/%d', passes, passes + failures);
        process.exit(failures);
    });
};

【问题讨论】:

    标签: node.js asynchronous callback mocha.js


    【解决方案1】:

    你的回调永远不会执行,因为你的代码没有给它执行的机会。关于 JavaScript 有一个基本原则你要么不知道,要么你暂时忘记了:JavaScript 代码是没有被抢占。这段代码保证是一个无限循环:

    while(!callbackFired){
        console.log('waiting...');
    }
    

    为什么?首先,因为request 必须在callbackFired 变为真之前实际完成HTTP 请求。当你调用request时,它的作用是发起HTTP请求,并记录当请求完成时,应该调用回调。其次,还记得我上面说的不抢占吗? JavaScript VM 在callbackFired 变为真之前进入循环,并开始执行循环。循环执行时可能会完成 HTTP 请求,但回调 仍然 不会执行。为什么?因为只要循环正在执行,JavaScript VM 就无法控制回调(没有抢占!)。为了让 VM 能够控制回调,您的代码必须返回,以便最终由 VM 启动的所有函数都返回,然后 VM 有机会处理 HTTP 完成。

    在更正式的术语中(请参阅documentation here,您的代码不会让 VM 的事件循环有机会处理 HTTP 完成事件并控制您的回调。

    话虽如此,让您的记者异步工作还有其他考虑因素:

    1. 不要从你的记者那里拨打process.exit。如果您需要在进程退出时发生一些事情,您可以使用process.on('exit', ...

    2. 事件处理程序同步操作,因此您不能让runner.on('start', ...) 等待异步操作的结果。

      您可以做的是让runner.on('start', ...) 启动一个返回承诺的异步操作,然后在runner.on('pass', ...)(和'fail')中,您将使用此承诺执行更多异步操作。例如,

      module.exports = function (runner) {
          var passes = 0;
          var failures = 0;
          var init;
      
          runner.on('start', function() {
              init = async_op_returning_promise(...);
          });
      
      runner.on('pass', function(test){
          passes++;
          init.then(function (results) {
            // do something more...
          });
      });
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-27
      • 1970-01-01
      • 2013-04-02
      • 2012-08-21
      • 2012-01-08
      • 1970-01-01
      • 2014-01-15
      相关资源
      最近更新 更多