问题
I'm trying to write a custom mocha reporter that integrates with a 3rd party that has a HTTP API.
I took the basic example given of a custom reporter (https://github.com/mochajs/mocha/wiki/Third-party-reporters) and attempted to add a call to the API in the start event handler. However, the callback for my http request to the third party never seems to fire. Example code shown below.
I've tested that the request code works as expected in other contexts, it just seems to be within the event handler in the mocha reporter that nothing happens.
Note - please forgive the current hack to wait on the result of the callback, I just want to make sure I wasn't exiting before the callback had a chance to fire, I'll tidy this up when I get it working!
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:
Your callback never executes because your code does not give it a chance to execute. There's a basic principle you either do not know about JavaScript or that you have momentarily forgotten: JavaScript code is not preempted. This code is guaranteed to be an infinite loop:
while(!callbackFired){
console.log('waiting...');
}
Why? First, because request
has to actually complete the HTTP request before callbackFired
can become true. When you call request
, what it does is initiate the HTTP request and record that when the request is completed, the callback should be called. Second, remember I said no preemption above? The JavaScript VM gets to the loop before callbackFired
becomes true, and it starts executing the loop. It is possible that the HTTP request will complete while the loop is executing but the callback still won't execute. Why? Because the JavaScript VM cannot give control to the callback as long as the loop is executing (no preemption!). For the VM to be able to give control to the callback, your code has to return, so that eventually all functions that were started by the VM return, and the VM then gets the chance to handle the HTTP completion.
In more formal terms (see the documentation here, your code does not give the opportunity to the VM's event loop to process the HTTP completion event and give control to your callback.
This being said, there are other considerations to get your reporter working asynchronously:
Don't call
process.exit
from your reporter. If you need things to happen when the process exits, you can useprocess.on('exit', ...
.The event handlers operate synchronously so you cannot have
runner.on('start', ...)
wait for the result of an asynchronous operation.What you could do is have
runner.on('start', ...)
initiate an asynchronous operation that returns a promise and then inrunner.on('pass', ...)
(and'fail'
) you'd use this promise to perform more asynchronous operations. For instance,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... }); });
来源:https://stackoverflow.com/questions/34653702/how-do-i-make-asynchronous-calls-in-an-event-handler