Async: Setting up Event Handlers using Async

Created on 25 Oct 2016  ·  4Comments  ·  Source: caolan/async

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.

question

Most helpful comment

I think that is the craziest auto use case I've seen by the way :building_construction:

All 4 comments

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...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tlbtlbtlb picture tlbtlbtlb  ·  9Comments

alexbjorlig picture alexbjorlig  ·  11Comments

caub picture caub  ·  12Comments

pcman312 picture pcman312  ·  8Comments

rumkin picture rumkin  ·  22Comments