ECMAScript 6 natively provides the Promise object.

Promise objects represent events that will happen in the future and are used to pass messages from asynchronous operations.

Promise objects have the following two characteristics:

1. The state of the object is not affected by the outside world. A Promise object represents an asynchronous operation and has three states:

  • pending: the initial state, neither a success nor a failure state.
  • fulfilled: means the operation completed successfully.
  • rejected: means the operation failed.

Only the result of an asynchronous operation can determine which state it is currently in; no other operation can change this state. This is also the origin of the name Promise, whose English meaning is "promise," indicating that no other means can change it.

Once the state changes, it will not change again, and the result can be obtained at any time. There are only two possible changes for a Promise object's state: from Pending to Resolved, and from Pending to Rejected. Once either of these happens, the state is solidified and will never change again, always maintaining that result. Even if the change has already occurred, if you add a callback function to the Promise object later, you will still immediately get that result. This is completely different from events. The characteristic of an event is that if you miss it and listen to it later, you cannot get the result.

Pros and Cons of Promise

With Promise objects, asynchronous operations can be expressed in the flow of synchronous operations, avoiding deeply nested callback functions. In addition, Promise objects provide a unified interface, making it easier to control asynchronous operations.

Promise also has some disadvantages. First, a Promise cannot be cancelled. Once created, it executes immediately and cannot be cancelled midway. Second, if no callback function is set, errors thrown inside the Promise will not be reflected externally. Third, when in the Pending state, there is no way to know which stage the current progress is at (just started or about to complete).

Promise creation

To create a promise object, you can use new to call the Promise constructor to instantiate it.

Below are the steps for creating a promise:

var promise = new Promise(function(resolve, reject) { //Asynchronous Processing //After processing ends, call resolve or reject });

The Promise constructor contains one parameter and a callback with two parameters: resolve (resolution) and reject (rejection). Perform some operation (for example, asynchronous) in the callback. If everything is normal, call resolve; otherwise, call reject.

Example

var myFirstPromise = new Promise(function(resolve, reject){ //When the asynchronous code executes successfully, we call resolve(...); when it fails, we call reject(...). //In this example, we use setTimeout(...) to simulate asynchronous code; in actual coding, it might be XHR requests or some HTML5 API methods. setTimeout(function(){ resolve("Success!"); //Code executed normally! }, 250); }); myFirstPromise.then(function(successMessage){ //The value of successMessage is the value passed to the resolve(...) method call above. //The successMessage parameter does not necessarily have to be a string type; this is just an example. document.write("Yay! " + successMessage); });

Try it Yourself »

For an already instantiated promise object, you can call the promise.then() method, passing resolve and reject methods as callbacks.

promise.then() is the most commonly used method of promise.

promise.then(onFulfilled, onRejected)

The promise simplifies error handling. The above code can also be written like this:

promise.then(onFulfilled).catch(onRejected)

Promise Ajax

Below is an example of an Ajax operation implemented with a Promise object.

Example

function ajax(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var URL = "/try/ajax/testpromise.php"; ajax(URL).then(function onFulfilled(value){ document.write('Content:' + value); }).catch(function onRejected(error){ document.write('Error:' + error); });

Try it Yourself »

In the code above, the resolve method and the reject method are both called with arguments. Their arguments will be passed to the callback functions. The argument of the reject method is usually an instance of the Error object, while the argument of the resolve method, in addition to a normal value, may also be another Promise instance, as shown below.

var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); })

In the code above, p1 and p2 are both Promise instances, but p2's resolve method takes p1 as an argument. At this point, p1's state will be passed to p2. If p1's state is pending when called, then p2's callback function will wait for p1's state to change; if p1's state is already fulfilled or rejected, then p2's callback function will be executed immediately.


Promise.prototype.then method: chained operations

The Promise.prototype.then method returns a new Promise object, so a chained syntax can be used.

getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // proceed });

The code above uses the then method to specify two callback functions in sequence. After the first callback function completes, its return result will be passed as an argument to the second callback function.

If the previous callback function returns a Promise object, the latter callback function will wait until that Promise object has a result before it is further called.

getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { //Process comments });

This design makes nested asynchronous operations easy to rewrite, changing from the "horizontal development" of callback functions to "downward development."


Promise.prototype.catch method: catching errors

The Promise.prototype.catch method is an alias for Promise.prototype.then(null, rejection), used to specify a callback function when an error occurs.

getJSON("/posts.json").then(function(posts) { // some code }).catch(function(error) { //Handle errors that occur during the previous callback function's execution console.log('An error occurred!', error); });

Errors of Promise objects have a "bubbling" nature, passing backward until caught. That is, an error will always be caught by the next catch statement.

getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { // some code }).catch(function(error) { //Handle the errors of the first two callback functions });

Promise.all method, Promise.race method

The Promise.all method is used to wrap multiple Promise instances into a new Promise instance.

var p = Promise.all([p1,p2,p3]);

In the code above, the Promise.all method accepts an array as its argument, and p1, p2, and p3 are all instances of Promise objects. (The argument of the Promise.all method does not have to be an array, but it must have an iterator interface, and each member it returns must be a Promise instance.)

The state of p is determined by p1, p2, and p3, and there are two cases.

  • (1) Only when p1, p2, and p3 all become fulfilled will p's state become fulfilled. At this time, the return values of p1, p2, and p3 form an array that is passed to p's callback function.
  • (2) As long as one of p1, p2, and p3 is rejected, p's state becomes rejected. At this time, the return value of the first rejected instance will be passed to p's callback function.

The following is a concrete example.

//Generate an array of Promise objects var promises = [2, 3, 5, 7, 11, 13].map(function(id){ return getJSON("/post/" + id + ".json"); }); Promise.all(promises).then(function(posts) { // ... }).catch(function(reason){ // ... });

The Promise.race method also wraps multiple Promise instances into a new Promise instance.

var p = Promise.race([p1,p2,p3]);

In the code above, as long as one of p1, p2, and p3 is the first to change state, p's state changes accordingly. The return value of the Promise instance that first changes state is passed to p's return value.

If the arguments of the Promise.all method and the Promise.race method are not Promise instances, the Promise.resolve method described below will be called first to convert the arguments into Promise instances, and then further processing will be done.


Promise.resolve method, Promise.reject method

Sometimes existing objects need to be converted into Promise objects, and the Promise.resolve method serves this purpose.

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

The code above converts the deferred object generated by jQuery into a new ES6 Promise object.

If the argument of the Promise.resolve method is not an object with a then method (also known as a thenable object), it returns a new Promise object whose state is fulfilled.

var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello

The code above creates an instance p of a new Promise object, whose state is fulfilled, so the callback function will execute immediately. The argument of the Promise.resolve method is the parameter of the callback function.

If the argument of the Promise.resolve method is an instance of a Promise object, it will be returned as-is.

The Promise.reject(reason) method also returns a new Promise instance, whose state is rejected. The parameter reason of the Promise.reject method will be passed to the instance's callback function.

var p = Promise.reject('An error occurred.'); p.then(null, function (s){ console.log(s) }); //An error occurred.

The code above creates an instance of a Promise object, whose state is rejected, and the callback function will execute immediately.

Reference links:

  • https://wohugb.gitbooks.io/ecmascript-6/content/docs/promise.html
  • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
  • http://qingtong234.github.io/2016/01/14/javascript%E4%B8%AD%E7%9A%84promise%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B-1/