How to Convert an Existing Callback API to Promises

How do I convert an existing callback API to promises?

Promises have state, they start as pending and can settle to:

  • fulfilled meaning that the computation completed successfully.
  • rejected meaning that the computation failed.

Promise returning functions should never throw, they should return rejections instead. Throwing from a promise returning function will force you to use both a } catch { and a .catch. People using promisified APIs do not expect promises to throw. If you're not sure how async APIs work in JS - please see this answer first.

1. DOM load or other one time event:

So, creating promises generally means specifying when they settle - that means when they move to the fulfilled or rejected phase to indicate the data is available (and can be accessed with .then).

With modern promise implementations that support the Promise constructor like native ES6 promises:

function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}

You would then use the resulting promise like so:

load().then(function() {
// Do things after onload
});

With libraries that support deferred (Let's use $q for this example here, but we'll also use jQuery later):

function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}

Or with a jQuery like API, hooking on an event happening once:

function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}

2. Plain callback:

These APIs are rather common since well… callbacks are common in JS. Let's look at the common case of having onSuccess and onFail:

function getUserData(userId, onLoad, onFail) { …

With modern promise implementations that support the Promise constructor like native ES6 promises:

function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}

With libraries that support deferred (Let's use jQuery for this example here, but we've also used $q above):

function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}

jQuery also offers a $.Deferred(fn) form, which has the advantage of allowing us to write an expression that emulates very closely the new Promise(fn) form, as follows:

function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}

Note: Here we exploit the fact that a jQuery deferred's resolve and reject methods are "detachable"; ie. they are bound to the instance of a jQuery.Deferred(). Not all libs offer this feature.

3. Node style callback ("nodeback"):

Node style callbacks (nodebacks) have a particular format where the callbacks is always the last argument and its first parameter is an error. Let's first promisify one manually:

getStuff("dataParam", function(err, data) { …

To:

function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}

With deferreds you can do the following (let's use Q for this example, although Q now supports the new syntax which you should prefer):

function getStuffAsync(param) {
var d = Q.defer();
getStuff(param, function(err, data) {
if (err !== null) d.reject(err);
else d.resolve(data);
});
return d.promise;
}

In general, you should not promisify things manually too much, most promise libraries that were designed with Node in mind as well as native promises in Node 8+ have a built in method for promisifying nodebacks. For example

var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only

4. A whole library with node style callbacks:

There is no golden rule here, you promisify them one by one. However, some promise implementations allow you to do this in bulk, for example in Bluebird, converting a nodeback API to a promise API is as simple as:

Promise.promisifyAll(API);

Or with native promises in Node:

const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
.reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});

Notes:

  • Of course, when you are in a .then handler you do not need to promisify things. Returning a promise from a .then handler will resolve or reject with that promise's value. Throwing from a .then handler is also good practice and will reject the promise - this is the famous promise throw safety.
  • In an actual onload case, you should use addEventListener rather than onX.

JavaScript convert callback to promise

Just return a promise from your getBooks function.

getBooks : function()
{
return new Promise((resolve, reject) => {
database.ref('/books/').on('value', (datasnapshot) => {
var books = datasnapshot.val();
books ? resolve(books) : reject();
});
});
}

And you can use it, in this way...

app.get('/api/books', function(req, res)
{
DataBase.getBooks()
.then((books) => res.send(books))
.catch(() => console.log('!!. ERROR'));
})

Simple Callback to Promise Conversion

You can just convert the functions into async functions/promises.

Some good documentation about that can be found here .

But I guess you also want to simulate some delay. So I added additional function for that (it might help with understanding).

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))

const sumWithDelay = async (a, b) => {
console.log("Delay started")
await sleep(3000)
console.log("Delay ended")
return a + b;
}

const sum = async (a, b) => {
return a + b;
}

const mul = async (a, b) => {
return a * b;
}

let dne = async function() {
return 'it does not exist';
};

let doCalculation = function(route) {
switch (route) {
case 'sum':
return sum;
case 'sumWithDelay':
return sumWithDelay;
case 'mul':
return mul;
default:
return dne;
}
};

doCalculation('sumWithDelay')(2, 2).then(res => console.log(res)); //4
doCalculation('sum')(3, 4).then(res => console.log(res)); // 7
doCalculation('mul')(3, 4).then(res => console.log(res)); // 12
doCalculation('dne')(3, 4).then(res => console.log(res)); // it does not exist

How to convert a callback code to promise in ES6

The existing answers fall prey to the deferred anti-pattern. I would avoid this approach as it is unnecessarily verbose and doesn't take advantage of the full Promise API. Another of the answers uses promisification. It's really only necessary to use promisification when you are not able to change the code that is written using the callback-style, for example with third-party scripts.

You are asking two questions, the second of which is why do Promises behave the way that you have seen in your given example. To find answers to this question I suggest you use the many existing questions on SO of this nature. For example, aren't Promises just callbacks?

As for your first question, of how to refactor your code to use Promises, here is my suggestion:

module.exports = (x, y) => {
if (x < 0 || y < 0) {
return Promise.reject(new Error('Rectangle dimensions are wrong.'));
} else {
return Promise.resolve({
perimeter() {
return (2 * (x + y));
},
area() {
return (x * y);
},
});
}
};

// e.g. success
createRectangle(10, 10)
.then(rect => {
console.log(rect.area()) //=> 100
})

// e.g. failure
createRectangle(-1, -1)
.catch(err => {
console.log(err) //=> "Error: Rectangle dimensions are wrong."
})

As the function doesn't itself depend on the completion of an asynchronous operation we can use the helper methods Promise#resolve and Promise#reject to return a Promise from the function that represents the success or failure of creating the "rectangle" object. These produce a new Promise whose status is resolved or rejected with either a value or an error, respectively.

Resolving a promise after another callback is executed

Since functionA returns a Promise and does not use await it is not async.

Since functionB takes a callback you can just pass down your resolve function.

Either with an arrows function:

function functionB(url, cb) {
// Simulate Some Callback Behaviour
cb(`${url} + 10`);
}


function functionA(url) {
return new Promise((resolve, reject) => {
functionB(url, (result) => {
resolve(result);
});
});
}

function process(result) {
console.log(result);
}


functionA("sample.com").then((result) => process(result));

Convert callback to Promise

The desired result of callEndPointWrapper seems to be a function (callEndPoint) that does some asynchronous work which you can further invoke to do something.

In your callback approach you are producing this callEndPoint function.

----> callEndPointWrapper returns callEndPoint which does async work.

In your promise based approach on the other hand, you are attempting to produce the result of callEndPoint rather than return callEndPoint itself. In fact, callEndPoint is never invoked within the Promise constructor.

----> callEndPointWrapper returns a promise which never resolves and internally creates the callEndPoint function that does nothing because it's never invoked.

It's important to note that a single call to callEndPointWrapper is not async. The actual async part (assuming client methods are async) happens in callEndpoint so an async invocation for your callback-based approach would be something like:

callEndPointWrapper()(
function callback(responseFromEndpoint) {
// ...
}
)

// or something like
let caller = callEndPointWrapper();
caller(function callback(responseFromEndpoint) {
// ...
});

From this follows that the promise based approach would require two invocations as well:

await callEndPointWrapper()(); // call a wrapper which returns a function which returns a promise

A functionally equivalent (in terms of the produced result) promise-based code for your callback code is the following:

function callEndPointWrapper(): any {
return function callEndpoint() {
return new Promise((resolve, reject) => {
try {
client[methodName](req, options, (err, result) => {
if (err) return reject(err);
resolve(result);
});
} catch (err) {
// Note: this rejection will only happen on any sync errors
// with the above code, such as trying to invoke a non-existing
// method on the client. This type of error is a programmer error
// rather than an operational error in the system so you should
// consider if such errors should even by caught by your code.
reject(err);
}
});
};
}

However, this leads to the question of what's the point of having the wrapper function at all if you're not passing any config options to be available in the closure of the callEndpoint function?

Based on your example usage, all you need is the callEndpoint method.


It comes to mind that you might be using something like co which allows you to yield functions (thunks) and invokes them internally with a callback.

So when you do

yield callEndpointWrapper()

you are actually calling

yield function callEndpoint(callback) {
// ...
}

And then co does some magic under the hood for you.

This is a pattern that is deprecated by co and is overall ill-advised to use. Not to mention that it's very confusing (and ugly IMHO) behavior that requires knowledge of the particular library.

To use promises with co, you don't need the wrapper function. Just yield or await the result of calling callEndPoint (the same one from my promise example above), which will be a promise.

yield callEndPoint()
// or
await callEndPoint()


Related Topics



Leave a reply



Submit