I'm wondering how to support Promise
's and Callbacks in an API. So far I've read through a few articles to try to implement however all these articles use deferred or a third party Promise library that has Deferred's. I want to use Native ES Promises. I wanted to know how to implement something like this. Please do not reference Promisify or any third party library as I actually want to implement this. The function signature is like the following:
function (callback) {
if (callback) {
// wrap callback and return Promise thats thenable
}
// return a Promise thats thenable
}
This is something I had in my head for how something should go but I'm not sure how to implement. If you have experience or know how to do this please reply as I'd like to learn.
Without further context to Question, not entirely certain what expected result is from function other than "thenable".
The simplest approach would be to utilize Promise.resolve()
or Promise.reject()
; though you could also use Promise.all()
, Promise.race()
or new Promise()
constructor to return a "thenable".
function fn(callback) {
if (callback) {
// wrap callback and return Promise thats thenable
return Promise.resolve(callback())
}
// return a Promise thats thenable
return Promise.resolve()
}
fn().then(function(data) {
console.log("no callback passed:", data); // `data` : `undefined`
})
fn(function() {return 123})
.then(function(data) {
console.log("callback passed:", data); // `data`: `123`
})
One possibility is to do like jQuery.ajax()
does and you always return a promise and, if the optional callback is present, then you call it too.
Here's one idea for that when your internal async operation does not use promises:
function myAPIxxx(arg1, arg2, callback) {
callback = callback || function() {};
return new Promise(function(resolve, reject) {
someAsyncOperation(arg1, arg2, function(err, data) {
if (err) {
reject(err);
callback(err);
} else {
resolve(data);
callback(null, data);
}
})
});
}
Since much of this is boilerplate, you could create a common wrapper that would do this for all your API functions.
If your internal operations are all using promises, then it's a little easier.
function myAPIxxx(arg1, arg2, callback) {
callback = callback || function() {};
return someAsyncOperation(arg1, arg2).then(callback.bind(null, null), callback);
}
If you already have a callback-based API that follows the node.js async callback calling convention, then you could simply call a promisify function on each of your API calls to create a new promise-based interface. I don't really understand why you suggest not mentioning promisify because if you already have a callback-based API and you want to support both, using a promisify function on each of your API calls is the cleanest, most foolproof way to add a promise-based interface.
If you're curious, here's a promisify function that accepts your existing callback API function and returns a new promise-based function:
function promisify(fn, obj) {
if (typeof fn !== "function") {
throw new Error("fn argument to promisify() must be function");
}
// obj is optional and may be undefined
// if present, it will be used as context to call fn as in obj.fn()
return function(/* args */) {
// make copy of arguments object into a real array in a way that
// does not prevent interpreter optimizations
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return new Promise(function(resolve, reject) {
// add our callback function at the end of the args list
var resultMany;
args.push(function(err, result) {
if (err) {
reject(err);
} else {
// if 0 or 1 result, then just return it as a simple value
if (arguments.length <= 2) {
resolve(result);
} else {
// if more than one result came with the callback function,
// then put it into an array so we can resolve with a single value (the array of results)
// skip the first argument which is the err value
resultMany = new Array(arguments.length - 1);
for (var i = 0; i < arguments.length - 1; i++) {
resultMany[i] = arguments[i + 1];
}
resolve(resultMany);
}
}
});
// call original function with our callback as last argument
fn.apply(obj, args);
});
}
}
So, if you had a function called myAPIxxx()
that was callback based, you could create a new one that was promise-based like this:
var myAPIxxxAsync = promisify(myAPIxxx);
If all your APIs are properties on a single object, you could just loop over the API and create a new promisified version of the existing API with one loop.
And, here's a function that would loop over an API object and create promisified versions of each with a different suffix on them:
function promisfyObj(obj, suffix) {
var asyncName, method, promiseSuffix, type = typeof obj;
var hasOwn = Object.prototype.hasOwnProperty;
if (!(type === "function" || type === "object")) {
throw new Error("first argument to promisifyObj() must be function or object");
}
if (suffix && typeof suffix !== "string") {
throw new Error("second argument to promisifyObj() must be a string or not present at all");
}
promiseSuffix = suffix ? suffix : "Async";
for (method in obj) {
if (typeof obj[method] === "function" && hasOwn.call(obj, method)) {
asyncName = method + promiseSuffix;
if (!(asyncName in obj)) {
obj[asyncName] = promisify(obj[method], obj);
}
}
}
return obj;
}
So, if all your API functions were on a common object such as myAPI.someFunc()
, then you could add promisified versions of every API like this:
promisifyObj(myAPI);
Promises in ES2015 are very easy to use
var everyThingIsOk = true;
// here you are defining native ES2015 promise with callback function which is your async operation;
var promise = new Promise(function(resolve,reject){
if(everyThingIsOk){ // if task has done you should call resolve function with data as parameter;
resolve("this async task has been successfully done");
}
else{ // if not you should return reject function with reason that can help you;
reject("there were some errors during execution or in logic");
}
})
promise.then(function(result){ // method resolve will call callback of then
alert(result)
})
.catch(function(error){ // method reject will call callback of catch
alert(error)
})
so for callback you need methods resolve
and reject
;
and you can do so
Promise.resolve(callback)
and Promise.reject(callback)
and return these statements from your function