22

My entire project uses (Bluebird) Promises, but there's one particular library that uses EventEmitter.

I want to achieve something like:

Promise.on('connect', function() {
    x.doSomething();
}).then(function() {
    return new Promise(function(resolve) {
        y.doAction(resolve); // this will result in `eventB` getting emitted
    });
}).on('eventB', function() {
    z.handleEventB();
}).then(function() {
    z.doSomethingElse();
});

I read the answer to EventEmitter in the middle of a chain of Promises. That gives me a way to execute the callback for 'connect' event. Here's where I have got so far

var p = new Promise(function(resolve) {
    emitter.on('connect', resolve);
});
p.on = function() {
    emitter.on.apply(emitter, arguments);
    return p;
};
p.on('connect', function() {
    x.doSomething();
}).then(function() {
    return new Promise(function(resolve) {
        y.doAction(resolve); // this will result in eventB getting emitted
    });
});

Now how to chain further for 'eventB' ?

Community
  • 1
  • 1
Jaydeep Solanki
  • 2,895
  • 5
  • 36
  • 50

2 Answers2

29

I assume you want to do a different chain of things for each event. Even if eventB is triggered by the actions of connect, you can treat it like another stream of logic.

Side note: To avoid confusion for you and anyone else who has to read this codebase, I'd recommend against supplementing promises with additional methods unless you are very thorough about documenting them.

From your example, it seems like the following would work.

var Promise = require( 'bluebird' )
var emitter = someEmitter()
var connected = new Promise( function( resolve ){
    emitter.on( 'connect', resolve )
})

var eventBHappened = new Promise( function( resolve ){
    emitter.on( 'eventB', resolve )
})

connected.then( function(){
    return x.doSomething()
}).then( function(){
    return y.doSomethingElse() // will trigger `eventB` eventually
})

// this promise stream will begin once `eventB` has been triggered
eventBHappened.then( function(){ 
    return z.doSomething()
})

If you'd like to simplify this constant

var p = new Promise( function( resolve ){
    emitter.on( 'something', resolve )
})

You can use something like this

function waitForEvent( emitter, eventType ){
    return new Promise( function( resolve ){
        emitter.on( eventType, resolve )
    })
}

Which turns the code solution above into

var Promise = require( 'bluebird' )
var emitter = someEmitter()

function waitForEvent( eventEmitter, eventType ){
    return new Promise( function( resolve ){
        eventEmitter.on( eventType, resolve )
    })
}

waitForEvent( emitter, 'connect' ).then( function(){
    return x.doSomething()
}).then( function(){
    return y.doSomethingElse() // will trigger `eventB` eventually
})

// this promise stream will begin once `eventB` has been triggered
waitForEvent( emitter, 'eventB' ).then( function(){ 
    return z.doSomething()
})

And because functions in Javascript capture the scope where they were defined, this code could be further simplified to

var Promise = require( 'bluebird' )
var emitter = someEmitter()

function waitForEvent( type ){
    return new Promise( function( resolve ){
        //emitter has been captured from line #2
        emitter.on( type, resolve ) 
    })
}

waitForEvent( 'connect' ).then( function(){
    return x.doSomething()
}).then( function(){
    return y.doSomethingElse() // will trigger `eventB` eventually
})

// this promise stream will begin once `eventB` has been triggered
waitForEvent( 'eventB' ).then( function(){ 
    return z.doSomething()
})
JoshWillik
  • 2,624
  • 21
  • 38
  • 3
    Can't promises only be resolved once? I don't think this would work for multi-event APIs. – Chris Grimmett Nov 19 '16 at 07:42
  • @Grimtech you're right. If you need to listen to an event multiple times, they're not the right tool – JoshWillik Nov 19 '16 at 07:45
  • 2
    use `.once` instead of `.on`, that way if an event is fired more than once, it won't break your promise (of course, use with care) – Nepoxx May 02 '17 at 18:27
  • You can call `resolve()` inside a promise multiple times, but `.once` is still a good idea, since whatever is awaiting the Promise's resolution will only ever get the first value it resolves to. – Lee Benson Apr 06 '18 at 22:32
1

I faced with the same problem and wrote a tiny promise-wrapping library (controlled-promise) that allows to promisify event emitters. The solution for your example is:

const Promise = require('bluebird');
const ControlledPromise = require('controlled-promise');

const emitter = someEmitter();
const waiting = new ControlledPromise();

function waitForEvent(type) {
    return waiting.call(() => {
       emitter.once(type, event => waiting.resolve(event));
    });
}

waitForEvent('connect')
    .then(() => x.doSomething())
    .then(() => waitForEvent('eventB'))
    .then(() => z.doSomethingElse());

The benefits of such approach:

  • automatic return of existing promise while it is pending
  • easy access to resolve() / reject() callbacks
vitalets
  • 4,675
  • 1
  • 35
  • 35