Node7 starts supporting async/await with the –harmony_async_await parameter, which is a haven for asynchronous calls because it can write asynchronous programs in synchronous form of code. However, Node’s callback model is deeply ingrained, and the “callback hell” structure is what’s driving Promise and ES6. However, from hell to heaven, is not a step!

Async /await is based on a Promise, not a callback, so to get out of callback hell, first change the callback implementation to a Promise implementation — the problem is that many Node library functions and many other third party library functions are implemented using callbacks. How easy is it to change them all to Promise implementations?

Use third-party libraries to get out of hell

Async

There is, of course, a way around this, such as the Async library that “flattens” deep callbacks by async.waterfall(). Of course it’s not implemented with Promises, but with its flattening work as a foundation, encapsulating Promises is much simpler.

The following is an example given in the Async official documentation

async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
        // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
});

It’s also easy to encapsulate it as a Promise:

// Using the async. Waterfall handler sequence // and encapsulates the end result as a Promise Function PromiseWaterfall (Series) {return new Promise((resolve, reject) => { async.waterfall(series, function(err, result) { if (err) { reject(err); } else { resolve(result); }}); }); } promiseWaterfall([function(callback) {callback(null, "one", "two");}, function(arg1, arg2,) callback) { // arg1 now equals 'one' and arg2 now equals 'two' callback(null, "three"); }, function(arg1, callback) { // arg1 now equals 'three' callback(null, "done"); } ]).then(result => { // result now equals 'done' });

Q

Q is also a common Promise library, providing a number of utility functions to handle Nodelike callbacks, such as q.nfcall (), q.nfapply (), q.denodeify (), and so on.

Where q.denodeify (), alias q.nfbind (), converts a Node callback style function into a Promise style function. Although the transformed function returns not a native Promise object, but an object of a Promise class implemented internally by Q, we can call it a Promise alike object.

The use of q.denodeify () is simple and simply encapsulates the Node-style functions, as shown in the official documentation below

var readFile = Q.nfbind(FS.readFile);
readFile("foo.txt", "utf-8").done(function (text) {
    // do something with text
});

It should be noted that although the function wrapped with q.denodeify () returns a Promise alike object, I have tested that it can be used for await operation.

[Note 1] : “await” is described on the MDN as “operator”, that is, operator, so here we say “await the operation”, or we can say “await the expression”.

Bluebird

For Jser, Bluebird is no stranger. It provides transitions to Node-style functions, such as Promise.promisify() and Promise.promisifyAll(), which are similar to the aforementioned Q.Denodeify (). Note that the Promise mentioned here is not a native Promise, but is implemented by Bluebird and is usually referenced with the following statement:

const Promise = require("bluebird").Promise;

To distinguish it from the native Promise, you can also change it to

const BbPromise = require("bluebird").Promise;

Promise.promisifyAll() is a more special method that takes an object as an argument and handles all of its methods in a Promise style, but you can also specify a filter that will only handle certain methods (see the official document for details).

Like q.denodeify (), the function processed by Bluebird’s Promise.promisify() or Promise.promisifyAll() returns a Promise alike object, and, Can also be used for await operation.

Get yourself out of hell

ES6 already provides a native Promise implementation, and it doesn’t seem worthwhile to reference a third-party library just to “get out of hell.” If you can wrap your own callback style into a Promise style with a small amount of code, why not implement one yourself?

Put it in perspective, what does being a promisify() have to do

[1] >definepromisify()

Promisify () is a transition function whose argument is a callback style function and whose return value is a Promise style function, so either argument or return value is a function

Function promisify(func) {return function() {promisify() {promisify(); }; }

[2] >The returned function needs to be returnedPromiseobject

Since promisify() is a promise-style function, its return value should be a Promise object, so promisify() is a promise-style function

function promisify(func) {
    return function() {
        return new Promise((resolve, reject) => {
            // TODO
        });
    };
}

[3] >Promise is calledfunc

Needless to say, the TODO part above needs to implement the call to func and call resolve() and reject() appropriately based on the result.

function promisify(func) { return function() { return new Promise((resolve, reject) => { func((err, result) => { if (err) { reject(err); } else { resolve(result); }}); }); }; }

(err, result) => {} The Node callback style callback function has an error object as the first argument. NULL means no error, so (err, result) => {}.

[4] >Combined with parameters

The above call does not add processing to the arguments. For Node callback style functions, the first n arguments are usually required by the internal implementation, and the last argument is the callback function. It’s easy to implement using ES6’s mutable arguments and extended data syntax

Function promisify(func) {return function(promisify(...)); args) { return new Promise((resolve, reject) => { func(... args, (err, result) => { if (err) { reject(err); } else { resolve(result); }}); }); }; }

At this point, a complete promisify() is implemented.

[5] >implementationpromisifyArray()

PromisifyArray () is used for batching a set of functions with a callback style list of arguments and a corresponding Promise style list of functions. Getting to promisifyArray() is pretty easy after achieving promisifyArray().

function promisifyArray(list) {
    return list.map(promisify);
}

[6] >implementationpromisifyObject()

PromiseIfyObject () is a kind of object that is supposed to be used as a promisifyObject(), but is not a pointer to this object. Here is a simplified implementation of promisifyObject(), see the comments in the code for more details.

Function promisifyObject(obj, suffix = "promisify") { Function promisify(func) {function promisify(func) = function promisify(func); function promisify(func) = function promisify(func); function promisify(func) = function promisify(func); Args) {return new Promise((resolve, reject) => {func.call(this, reject) =>; args, (err, result) => { if (err) { reject(err); } else { resolve(result); }}); }); }; } const Keys = [];} const Keys = []; for (const key in obj) { if (typeof obj[key] === "function") { keys.push(key); }} // Attach the converted function to the original object, // to ensure that when called, the reference to this is correct. // To avoid overwriting the original function, add a suffix. keys.forEach(key => { obj[`${key}${suffix}`] = promisify(obj[key]); }); return obj; }

Heaven is just around the corner

Out of hell, not far from heaven. I’ve already explained the relationship between async/await and Promise in a previous post on Understanding JavaScript async/await. We’ve already spent a lot of space converting callback style functions to Promise style functions, so the next step is to do the async/await practice.

Encapsulate the promisify correlation function into a module

Since it is used in Node, promisify(), promisifyArray() and promisifyObject() are better encapsulated in one Node module. Now that you’ve defined the three functions, you just need to export them

module.exports = { promisify: promisify, promisifyArray: promisifyArray, promisifyObject: promisifyObject }; // const {promisify, promisifyArray, promisifyObject} = require("./ promisifyObject ");

Because all three functions are independent and can be exported as an array,

module.exports = [promisify, promisifyArray, promisifyObject]; // const [promisify, promisifyArray, promisifyObject] = require("./ promisifyObject ");

Simulate an application scenario

In this simulated application scenario, an operation is required, including four steps (all asynchronous operations).

  1. first()Get a user ID
  2. second()Gets the user’s information based on the user ID
  3. third()Gets the user’s score based on the user ID
  4. last()Outputs user information and scores

Steps 2 and 3 can be parallel.

The data structure used for this scenario is defined as follows

class User { constructor(id) { this._id = id; this._name = `User_${id}`; } get id() { return this._id; } get name() { return this._name; } get score() { return this._score || 0; } set score(score) { this._score = parseInt(score) || 0; } toString() { return `[#${this._id}] ${this._name}: ${this._score}`; }}

Use setTimeout to simulate asynchrony

Define a toAsync() to simulate normal functions as asynchronous functions. You can skip setTimeout().

function toAsync(func, ms = 10) {
    setTimeout(func, ms);
}

Simulate the four steps in a callback style

Function first(callback) {toAsync(() => {const ID = parseInt(Math.random() * 9000 + 1000);  callback(null, id); }); } function second(id, callback) {toAsync(() =>;} function second(id, callback) {toAsync(() =>; }); } function third(id, callback) {toAsync(() =>;} function third(id, callback) {toAsync(() =>; }); } function last(user, score, callback) {toAsync(() => {user.score = score; console.log(user.toString()); if (callback) { callback(null, user); }}); }

And, of course, there’s an export

module.exports = [first, second, third, last];

Async/await practice

const [promisify, promisifyArray, promisifyObject] = require("./promisify"); const [first, second, third, last] = promisifyArray(require("./steps")); Async function main() {const userId = "await first()"; async function main() {const userId = "await first()"; // To wrap multiple parallel processing into a single Promise const [user, score] = await Promise. All ([second(userId), third(userId)]); last(user, score); } main();