Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert an existing callback API to promises?

I want to work with promises but I have a callback API in a format like:

1. DOM load or other one time event:

window.onload; // set to callback ... window.onload = function() {  }; 

2. Plain callback:

function request(onChangeHandler) {     ... } request(function() {     // change happened     ... }); 

3. Node style callback ("nodeback"):

function getStuff(dat, callback) {     ... } getStuff("dataParam", function(err, data) {     ... }) 

4. A whole library with node style callbacks:

API; API.one(function(err, data) {     API.two(function(err, data2) {         API.three(function(err, data3) {             ...         });     }); }); 

How do I work with the API in promises, how do I "promisify" it?

like image 402
Benjamin Gruenbaum Avatar asked Mar 19 '14 22:03

Benjamin Gruenbaum


People also ask

Is callback and promise same?

A callback function is passed as an argument to another function whereas Promise is something that is achieved or completed in the future. In JavaScript, a promise is an object and we use the promise constructor to initialize a promise.

Why promises is used instead of callbacks?

They can handle multiple asynchronous operations easily and provide better error handling than callbacks and events. In other words also, we may say that, promises are the ideal choice for handling multiple callbacks at the same time, thus avoiding the undesired callback hell situation.

How do promises relate to callbacks?

A promise is a returned object where you attach callbacks, instead of passing callbacks into a function. the place where you attach the callback after a successful completion of a task is called, . then(). inside this you pass a callback through.


2 Answers

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.
like image 181
Benjamin Gruenbaum Avatar answered Sep 29 '22 11:09

Benjamin Gruenbaum


Today, I can use Promise in Node.js as a plain Javascript method.

A simple and basic example to Promise (with KISS way):

Plain Javascript Async API code:

function divisionAPI (number, divider, successCallback, errorCallback) {      if (divider == 0) {         return errorCallback( new Error("Division by zero") )     }      successCallback( number / divider )  } 

Promise Javascript Async API code:

function divisionAPI (number, divider) {      return new Promise(function (fulfilled, rejected) {          if (divider == 0) {             return rejected( new Error("Division by zero") )         }          fulfilled( number / divider )       })  } 

(I recommend visiting this beautiful source)

Also Promise can be used with together async\await in ES7 to make the program flow wait for a fullfiled result like the following:

function getName () {      return new Promise(function (fulfilled, rejected) {          var name = "John Doe";          // wait 3000 milliseconds before calling fulfilled() method         setTimeout (              function() {                 fulfilled( name )             },              3000         )      })  }   async function foo () {      var name = await getName(); // awaits for a fulfilled result!      console.log(name); // the console writes "John Doe" after 3000 milliseconds  }   foo() // calling the foo() method to run the code 

Another usage with the same code by using .then() method

function getName () {      return new Promise(function (fulfilled, rejected) {          var name = "John Doe";          // wait 3000 milliseconds before calling fulfilled() method         setTimeout (              function() {                 fulfilled( name )             },              3000         )      })  }   // the console writes "John Doe" after 3000 milliseconds getName().then(function(name){ console.log(name) }) 

Promise can also be used on any platform that is based on Node.js like react-native.

Bonus: An hybrid method
(The callback method is assumed to have two parameters as error and result)

function divisionAPI (number, divider, callback) {      return new Promise(function (fulfilled, rejected) {          if (divider == 0) {             let error = new Error("Division by zero")             callback && callback( error )             return rejected( error )         }          let result = number / divider         callback && callback( null, result )         fulfilled( result )       })  } 

The above method can respond result for old fashion callback and Promise usages.

Hope this helps.

like image 34
efkan Avatar answered Sep 29 '22 11:09

efkan