如何在事件处理程序中进行asynchronous调用?

我试图写一个自定义的摩卡记者,与一个HTTP API的第三方集成。

我接受了自定义记者( https://github.com/mochajs/mocha/wiki/Third-party-reporters )的基本示例,并试图在启动事件处理程序中添加对API的调用。 然而,我的HTTP请求callback到第三方似乎从未触发。 示例代码如下所示。

我已经testing了请求代码在其他上下文中按预期工作,它似乎是在摩卡记者的事件处理程序内,没有任何反应。

注意 – 请原谅当前的黑客等待callback的结果,我只是想确保我没有退出之前,callback有机会开火,我会收拾起来,当我得到它的工作!

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

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

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

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

更正式的说法(参见这里的文档 ,你的代码不会给VM的事件循环机会来处理HTTP完成事件,并控制你的callback。

这就是说,还有其他的考虑让你的记者asynchronous工作:

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

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

    你可以做的是有runner.on('start', ...)启动一个asynchronous操作,返回一个承诺,然后在runner.on('pass', ...) (和'fail' )使用这个承诺来执行更多的asynchronous操作。 例如,

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