Skip to content
On this page

Promise

In the world of JavaScript, all code is executed in a single-threaded manner.

Due to this "flaw," all network operations and browser events in JavaScript must be executed asynchronously. Asynchronous execution can be implemented using callback functions:

javascript
function callback() {
    console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // Calls callback function after 1 second
console.log('after setTimeout()');

Observing the execution of the above code, we can see in the Chrome console output:

before setTimeout()
after setTimeout()
(waiting 1 second)
Done

It is evident that asynchronous operations will trigger a function call at some point in the future.

AJAX is a typical asynchronous operation:

javascript
request.onreadystatechange = function () {
    if (request.readyState === 4) {
        if (request.status === 200) {
            return success(request.responseText);
        } else {
            return fail(request.status);
        }
    }
}

It's normal to write the callback functions success(request.responseText) and fail(request.status) within an AJAX operation, but it's not visually appealing and not conducive to code reuse.

Is there a better way to write this? For example, like this:

javascript
let ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

The benefit of this chainable syntax is that it first uniformly executes the AJAX logic without caring about how to handle the results, and then, based on whether the result is a success or a failure, calls the success or fail function at some future point in time.

As the ancients said, "A gentleman's promise is worth a thousand pieces of gold." This "promise to execute in the future" object in JavaScript is called a Promise object.

Promises have various open-source implementations and were standardized in ES6, with direct support from browsers. First, check if your browser supports Promises:

javascript
new Promise(function () {});

// Run the test directly:
console.log('Supports Promise!');

Let's first look at the simplest example of a Promise: generating a random number between 0 and 2. If it's less than 1, it will wait for a while and return success; otherwise, it will return failure:

javascript
function test(resolve, reject) {
    let timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}

This test() function has two parameters, both of which are functions. If it executes successfully, we call resolve('200 OK'), and if it fails, we call reject('timeout in ' + timeOut + ' seconds.'). We can see that the test() function only cares about its own logic and does not concern itself with how the specific resolve and reject will handle the results.

With the execution function, we can use a Promise object to execute it and obtain the success or failure result at some future point in time:

javascript
let p1 = new Promise(test);
let p2 = p1.then(function (result) {
    console.log('Success: ' + result);
});
let p3 = p2.catch(function (reason) {
    console.log('Failure: ' + reason);
});

The variable p1 is a Promise object responsible for executing the test function. Since the test function is executed asynchronously internally, when it executes successfully, we inform the Promise object:

javascript
// If successful, execute this function:
p1.then(function (result) {
    console.log('Success: ' + result);
});

When the test function fails, we inform the Promise object:

javascript
p2.catch(function (reason) {
    console.log('Failure: ' + reason);
});

Promise objects can be chained, so the above code can be simplified to:

javascript
new Promise(test).then(function (result) {
    console.log('Success: ' + result);
}).catch(function (reason) {
    console.log('Failure: ' + reason);
});

Let's test it out to see how Promise executes asynchronously:

javascript
// Clear logs:
let logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
    logging.removeChild(logging.children[logging.children.length - 1]);
}

// Output logs to the page:
function log(s) {
    let p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
}

new Promise(function (resolve, reject) {
    log('start new Promise...');
    let timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}).then(function (r) {
    log('Done: ' + r);
}).catch(function (reason) {
    log('Failed: ' + reason);
});

Log:

It is evident that the greatest benefit of Promise is the clear separation of execution code and result handling code in the asynchronous execution process:

                                   ┌──────────────────┐
                           then    │on_resolve(data) {│
                       ┌──────────▶│    // TODO       │
                       │           │}                 │
┌────────────────────────┐         └──────────────────┘
│   new Promise(async)   │
└────────────────────────┘         ┌──────────────────┐
             │         │           │on_reject(data) { │
             │         └──────────▶│    // TODO       │
      promise│             catch   │}                 │
             │                     └──────────────────┘

┌────────────────────────┐
│async(resolve, reject) {│
│    // TODO             │
│}                       │
└────────────────────────┘

Promises can do even more things. For instance, if there are several asynchronous tasks that need to be done sequentially (task 1 first, then task 2), if any task fails, the error handling function should execute.

To execute such asynchronous tasks sequentially without using Promises, one would have to write nested code layers. With Promises, we can simply write:

javascript
job1.then(job2).then(job3).catch(handleError);

Where job1, job2, and job3 are all Promise objects.

The following example demonstrates how to sequentially execute a series of tasks that require asynchronous computation to obtain results:

javascript
let logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
    logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
    let p = document.createElement('p');
    p.innerHTML = s;
    logging.appendChild(p);
}

// Returns the calculation result of input * input after 0.5 seconds:
function multiply(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' x ' + input + '...');
        setTimeout(resolve, 500, input * input);
    });
}

// Returns the calculation result of input + input after 0.5 seconds:
function add(input) {
    return new Promise(function (resolve, reject) {
        log('calculating ' + input + ' + ' + input + '...');
        setTimeout(resolve, 500, input + input);
    });
}

let p = new Promise(function (resolve, reject) {
    log('start new Promise...');
    resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
    log('Got value: ' + result);
});

The setTimeout can be seen as a function that simulates asynchronous execution such as network calls.

In addition to executing several asynchronous tasks sequentially, Promises can also execute asynchronous tasks in parallel.

Consider a chat system where we need to obtain user personal information and friend lists from two different URLs. These two tasks can be executed in parallel using Promise.all() as follows:

javascript
let p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
let p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// Execute p1 and p2 simultaneously, and then execute then when both are complete:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // Obtains an Array: ['P1', 'P2']
});

Sometimes, multiple asynchronous tasks are for fault tolerance. For example, when reading user personal information from two URLs simultaneously, we only need to obtain the result from the first one that returns. In this case, we can implement it using Promise.race():

javascript
let p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
let p2 = new Promise(function (resolve, reject) {


    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

Since p1 executes faster, the then() of the Promise will receive the result 'P1'. p2 will continue executing, but its result will be discarded.

By combining the use of Promises, we can execute many asynchronous tasks in both parallel and sequential ways.

Promise has loaded