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

时间:2016-01-07 11:13:26

标签: node.js asynchronous callback mocha

我正在尝试编写一个自定义的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);
    });
};

1 个答案:

答案 0 :(得分:2)

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

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

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

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

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

  1. 请勿向您的记者致电process.exit。如果您在流程退出时需要执行某些操作,则可以使用process.on('exit', ...

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

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

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