How to create a function that returns a promise which waits for an EventEmitter?
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
})()
Answer
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.
Related Questions
- → How to update data attribute on Ajax complete
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → Octobercms Component Unique id (Twig & Javascript)
- → Passing a JS var from AJAX response to Twig
- → Laravel {!! Form::open() !!} doesn't work within AngularJS
- → DropzoneJS & Laravel - Output form validation errors
- → Import statement and Babel
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM