JavaScript Promise Object
Category Programming Technology
ECMAscript 6 natively provides the Promise object.
The Promise object represents an event that will occur in the future, used to convey messages of asynchronous operations.
Characteristics of Promise Objects:
The state of the object is unaffected by external factors. A Promise object represents an asynchronous operation and has three states:
- pending: initial state, not a success or failure state.
- fulfilled: indicates that the operation was successfully completed.
- rejected: indicates that the operation failed.
Only the result of the asynchronous operation can determine which state it is in; no other operation can change this state. This is also where the name "Promise" comes from, as it means "commitment" in English, indicating that other means cannot alter it.
Once the state changes, it will not change again, and the result can be obtained at any time. The state change of a Promise object can only be from Pending to Resolved or from Pending to Rejected. Once either of these situations occurs, the state solidifies and will not change again, maintaining this result. Even if the change has already occurred, if you add a callback function to the Promise object later, you will immediately get this result. This is completely different from events; for events, if you miss them and then listen for them, you will not get the result.
Advantages and Disadvantages of Promises
With the Promise object, asynchronous operations can be expressed in the flow of synchronous operations, avoiding nested callback functions. Additionally, the Promise object provides a unified interface, making it easier to control asynchronous operations.
Promises also have some disadvantages. First, you cannot cancel a Promise; once it is created, it executes immediately and cannot be canceled midway. Second, if a callback function is not set, errors thrown inside the Promise will not be reflected externally. Third, when in the Pending state, it is impossible to know which stage of progress is currently underway (just started or about to complete).
Creating a Promise
To create a Promise object, you can use the new
keyword to instantiate it using the Promise constructor.
Here are the steps to create a promise:
var promise = new Promise(function(resolve, reject) {
// Asynchronous processing
// Call resolve or reject after processing
});
The Promise constructor takes one argument and a callback with two parameters: resolve and reject. Perform some operations in the callback (such as asynchronous operations), and if everything is normal, call resolve; otherwise, call reject.
Example
var myFirstPromise = new Promise(function(resolve, reject){
// We will call resolve(...), when the asynchronous code succeeds, and reject(...) when it fails
// In this example, we use setTimeout(...) to simulate asynchronous code, in real code this might be an XHR request or an HTML5 API method.
setTimeout(function(){
resolve("Success!"); // Code executes normally!
}, 250);
});
myFirstPromise.then(function(successMessage){
// The value is what was passed into the resolve(...) method.
// The successMessage parameter does not have to be a string; this is just an example.
document.write("Yay! " + successMessage);
});
For an already instantiated Promise object, you can call the promise.then()
method, passing resolve and reject methods as callbacks.
The promise.then()
method is the most commonly used method for promises.
promise.then(onFulfilled, onRejected)
Promises simplify error handling; the above code can also be written as:
promise.then(onFulfilled).catch(onRejected)
Promise Ajax
Below is an example of an Ajax operation implemented using 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 is: ' + value);
}).catch(function onRejected(error){
document.write('Error: ' + error);
});
In the above code, both the resolve and reject methods are called with parameters, which are passed to the callback functions. The reject method's parameter is usually an instance of the Error object, while the resolve method's parameter can 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 above code, both p1 and p2 are instances of the Promise object, but p2's resolve method takes p1 as a parameter, at which point p1's state is passed to p2. If p1's state is pending when called, p2's callback function will wait for p1's state to change; if p1's state is already fulfilled or rejected, p2's callback function will execute immediately.
Promise.prototype.then Method: Chained Operations
The Promise.prototype.then
method returns a new Promise object, so it can be written in a chain.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// proceed
});
The above code uses the then method to specify two callback functions in sequence. After the first callback function completes, its return result is passed as a parameter to the second callback function.
If the previous callback function returns a Promise object, the next callback function will wait until the Promise object has a result before further execution.
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 from "horizontal expansion" to "vertical expansion."
Promise.prototype.catch Method: Error Handling
The Promise.prototype.catch
method is an alias for Promise.prototype.then(null, rejection)
, used to specify a callback function for errors.
getJSON("/posts.json").then(function(posts) {
// some code
}).catch(function(error) {
// Handle errors that occurred during the previous callback function's execution
console.log('An error occurred!', error);
});
Errors in Promise objects have a "bubbling" nature and will be passed back until caught. That is, errors are always 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 errors from the previous 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 above code, Promise.all
takes an array as a parameter, where p1, p2, and p3 are instances of the Promise object. (The parameter for Promise.all
does not have to be an array, but it must have an iterator interface, and each member returned must be an instance of the Promise object.)
The state of p is determined by p1, p2, and p3, divided into two situations:
- (1) Only when the states of p1, p2, and p3 all become fulfilled will the state of p become fulfilled, and the return values of p1, p2, and p3 will form an array and be passed to the callback function of p.
- (2) If one of p1, p2, or p3 is rejected, the state of p becomes rejected, and the return value of the first rejected instance will be passed to the callback function of p.
Here is a specific 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 above code, as soon as one of p1, p2, or p3 changes state, p's state changes accordingly. The return value of the instance that changes state first is passed to p's return value.
If the parameters of Promise.all
and Promise.race
are not instances of the Promise object, the Promise.resolve
method described below is first called to convert the parameters into instances of the Promise object for further processing.
Promise.resolve Method, Promise.reject Method
Sometimes it is necessary to convert an existing object into a Promise object; the Promise.resolve
method serves this purpose.
The code above converts a jQuery deferred object into a new ES6 Promise object.
If the parameter 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 with a fulfilled state.
var p = Promise.resolve('Hello');
p.then(function (s){ console.log(s) }); // Hello
The code above creates a new Promise object instance p, which is in the fulfilled state, so the callback function is executed immediately, and the parameter of the Promise.resolve method is passed as the argument to the callback function.
If the parameter of the Promise.resolve method is an instance of a Promise object, it is returned unchanged.
The Promise.reject(reason) method also returns a new Promise instance with a rejected state. The reason parameter of the Promise.reject method is passed to the instance's callback function.
var p = Promise.reject('出错了');
p.then(null, function (s){ console.log(s) }); // 出错了 ```
The code above creates a Promise object instance with a rejected state, and the callback function is executed 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/