如何在事件处理程序中进行异步调用

How do I make asynchronous calls in an event handler

我正在尝试编写一个自定义 mocha 记者,它与具有 HTTP API 的第 3 方集成。

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

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

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

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);
    });
};

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

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

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

用更正式的术语来说(参见 documentation here,您的代码没有为 VM 的事件循环提供机会来处理 HTTP 完成事件并控制您的回调。

话虽这么说,但要让您的记者异步工作还有其他注意事项:

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

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

    你可以做的是让 runner.on('start', ...) 启动一个异步操作,return 是一个承诺,然后在 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...
        });
    });