I'm having trouble using async in one of my integration tests that involves event handlers. The problem is that the event handler needs to be set up (with a reference to an async provided callback) before the event is emitted. However the event handler code needs an extra callback right away.
Here is a simplified example showing the problem:
async.auto({
// This needs to run first, but it will finish second
event: [(next) => {
emitter.once('awesome', next);
}],
// This needs to finish near the end
checkResults: ['event', (results, next) => {
assert(results.event.status == 200);
assert(results.event.msg == 'cool');
// Do other async stuff...
somethingAsync(next);
}],
// This has to start second, but before the first one finishes
emit: ['event', (results, next) => {
event.emit('awesome', {msg: 'cool'}, next);
}],
checkEmit: ['emit', (results, next) => {
// some sort of check that can be async
},
], done);
event
has to start first, but won't finish until the emit
occurs. emit
needs to wait for event
to start, but not finish (aka just set up the handler). Thus this won't finish.
Is there a way to do this with the current async library? (in a clean manner)
Heres a solution I would like, assuming this could be implemented in async
async.auto({
listen: [(next, done) => {
client.on(done);
return next();
},
...
}, callback);
I could use parallel, running the emitters and the listeners (of which there can be multiple in a test) as an array of tasks. Taking the results, and running the checks as a second part. However, technically parallel isn't required to kick-off the tasks in order (though it likely will). Also, the code becomes less flat, especially in more complicated setups.
Hi @Saevon, thanks for the question!
A quick, clean way of doing that would be:
async.auto({
// This needs to finish near the end
checkResults: [(next) => {
return next(null, (results, next) => {
assert(results.event.status == 200);
assert(results.event.msg == 'cool');
// Do other async stuff...
// somethingAsync(next);
});
}],
// This needs to run first, but it will finish second
event: ['checkResults', (handler, next) => {
emitter.once('awesome', handler.checkResults);
return next();
}],
// This has to start second, but before the first one finishes
emit: ['event', (results, next) => {
// Should this be emitter.emit instead of event.emit?
event.emit('awesome', {msg: 'cool'}, next);
}],
checkEmit: ['emit', (results, next) => {
// the results of `checkResults` will be in `results.emit`
// as the handler on 'awesome' was passed the `next`
// callback from `emit`
// some sort of check that can be async
yourChecks(next);
}]
}, function done(err) {
// everything finished running, unless `err` !== `null`
});
Essentially, you're just swapping the event
and checkResults
dependency, which actually might be a little cleaner, as event
depends on the handler in checkResults
. checkResults
is now just passing the handler to event
.
The execution order would be:
checkResults --> event --> emit --> handler (passed to event from checkResults) --> checkEmit --> done
.
Let me know if anything's unclear.
yeah, that solves the issue. Thanks!
I think that is the craziest auto use case I've seen by the way :building_construction:
Agreed. Usually async callbacks and event emitters don't mix well...
Most helpful comment
I think that is the craziest auto use case I've seen by the way :building_construction: