How to create a function that returns a promise which waits for an EventEmitter?

- 1 answer

Ad

When working with a JS library that uses EventEmitters to signal results are returned after calling an initiating function, how can I wrap the library by using a function that calls the initiating function and then returns a Promise which waits for the emitted event?

Update: A unique identifier is used to match requests with returned results in this example.

For example:

var Class = (function() {

    Class = function() {
        self = this
        self.transactionid = undefined
        self.result = undefined

        self.client = new Client({
            clientId: 0,
            host: '127.0.0.1',
            port: 4001
        }).on('received', function (tokens) {
            console.info('%s %s', '<<< RECV <<<'.cyan, JSON.stringify(tokens))

            // I wish to return `tokens` here via the promise
            if( tokens[ 0 ] == self.transactionid ) 
              self.result = tokens[ 1 ]

        }).on('sent', function (tokens) {
            console.info('%s %s', '>>> SENT >>>'.yellow, JSON.stringify(tokens))
        })

        self.client.connect()

    }

    Class.prototype.request = function( id ) {
        return $q( function( resolve, reject ) {

            self.transactionid = 1000
            self.client.requestData( transactionid, { key: '1234' }, '', false)

            var data = '<want to wait for / return token data here>'            

            // possibly use a dictionary to track different transactions and results?

            resolve( data )
        })
    }

    return Class

})()
Ad

Answer

Ad

If you rephrase "return tokens via the promise", as "resolve a Deferred associated with this transaction id", then you end up with something very much like a regular promise cache - except here a Deferred cache is required.

Fortunately $q (like Q but unlike Bluebird or native promises) provides Deferreds for circumstances like this, where Promise creation and Promise settlement are loosely coupled or otherwise dislocated.

var Class = (function() {
    Class = function() {
        self = this;
        self.transactions = {}; // a cache of Deferred objects
        self.client = new Client({
            clientId: 0,
            host: '127.0.0.1',
            port: 4001
        }).on('received', function (tokens) {
            console.info('%s %s', '<<< RECV <<<'.cyan, JSON.stringify(tokens));
            self.transactions[tokens[0]].resolve(tokens[1]);
        }).on('sent', function(tokens) {
            console.info('%s %s', '>>> SENT >>>'.yellow, JSON.stringify(tokens));
        });
        self.client.connect();
    };
    Class.prototype.request = function(id) {
        if(!this.transactions[id]) {
            this.transactions[id] = $q.defer();// a Deferred object to be deferred in the onReceived handler
            this.client.requestData( id, { key: '1234' }, '', false);
        }
        return this.transactions[id].promise;
    }
    return Class;
})();

Error handling appears to be a bigger problem. You could reintroduce an onerror handler but unless its error object includes an id property, you have no key to retrive the corresponding Deferred from this.transactions.

So, either :

  • modify the Client() class to deliver an error object with an id property.
  • where the request is created, reject the Deferred after a timeout.

Maybe even both of the above.

Ad
source: stackoverflow.com
Ad