Reputation: 99960
I have this code that is part of a small API that I am writing for an NPM module called Poolio. The question I have seems to be a common question for those supporting error-first callbacks as well as promises- how do we support both while maintaining consisent APIs and consistent return values from the API? For example, if I conditionally return a promise from my API, depending on whether the consumer of my lib provides a callback, that is a little awkward in my opinion.
The consumer of the lib can provide a callback or use the Promise then function, but not both.
Here is a function exported by my lib, that I would like to promisify:
Pool.prototype.any = function (msg, cb) {
var workId = this.counter++;
var self = this;
return new Promise(function (resolve, reject) {
if (typeof cb === 'function') {
self.resolutions.push({
workId: workId,
cb: cb
});
}
else {
self.resolutions.push({
workId: workId,
resolve: resolve,
reject: reject
});
}
if (this.available.length > 0) {
var cp = this.available.shift();
cp.workId = workId;
cp.send(msg);
}
else {
self.msgQueue.push({
workId: workId,
msg: msg
});
}
});
};
my question is - if the user provides a callback function in the original function arguments, how can I resolve the promise without calling 'then'? Sorry it's hard to explain but hopefully you can understand.
also there is this interesting question: Do never resolved promises cause memory leak?
Upvotes: 6
Views: 12564
Reputation: 8835
I think you are trying to do something like this:
function Pool() {
this.counter = 0
this.resolutions = []
this.available = []
this.msgQueue = []
}
Pool.prototype.any = function(msg, cb) {
const self = this;
const regCB = (msg, resolve, reject) => {
const workId = self.counter++;
self.resolutions.push({
workId,
resolve,
reject,
})
if (self.available.length > 0) {
const cp = self.available.shift();
cp.workId = workId;
cp.send(msg);
} else {
self.msgQueue.push({
workId,
msg,
});
}
}
const promise = new Promise((resolve, reject) => {
if (typeof cb === 'function') {
resolve = data => cb(null, data)
reject = err => cb(err, null)
}
regCB(msg, resolve, reject)
resolve(msg)
})
return promise
}
const fnc = (err, data) => {
console.log('fnc', {
data
})
}
const promise = Promise.resolve(3)
const pool = new Pool
pool.any('with_function', fnc)
const poolPromise = pool.any('with_promise')
poolPromise.then((data) => {
console.log('poolPromise', {
data
})
})
Upvotes: 0
Reputation: 13662
Actually, it's a pretty common thing APIs do (mongodb-driver example). Basically, write a private function accepting a callback, write a public function checking for cb and writing it if necessary. Using the code from your github (_any
might need a refactoring, you don't need to check if cb is a function for example and maybe other things too):
// private function
var _any = function(msg, cb) {
if (this.kill) {
console.log('warning: pool.any called on pool of dead/dying workers');
return;
}
debug('current available pool size for pool_id ' + this.pool_id + ' is: ' + this.available.length);
var workId = this.counter++;
if (typeof cb === 'function') {
this.resolutions.push({
workId: workId,
cb: cb
});
} else {
workId = -1;
}
if (this.available.length > 0) {
var cp = this.available.shift();
cp.workId = workId;
cp.send(msg);
} else {
this.msgQueue.push({
workId: workId,
msg: msg
});
}
};
// public exposed function
Pool.prototype.any = function(msg, cb) {
if (typeof cb === 'function') {
// cb is provided, no action is required here
return _any(msg, cb);
}
// no cb, wrap the call inside a Promise and provide a cb
return new Promise(function(resolve, reject) {
_any(msg, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
}
Upvotes: 1
Reputation: 113876
It's actually very straightforward. Only you may have missed it because it's hidden amongst that tangle of code.
Basically you do this:
var promise = new Promise(function (resolve, reject) { /*....*/});
if (typeof cb === 'function') {
promise.then(cb);
} else {
return promise;
}
Upvotes: 2