Promise 对象
# The Promise Object
# The Meaning of Promise
Promise is a solution for asynchronous programming that is more reasonable and powerful than traditional solutions -- callback functions and events. It was first proposed and implemented by the community, and ES6 incorporated it into the language standard, unified its usage, and natively provided the Promise object.
A Promise, simply put, is a container that holds the result of an event that will end in the future (usually an asynchronous operation). Syntactically, a Promise is an object from which you can obtain messages about asynchronous operations. Promise provides a unified API, allowing all kinds of asynchronous operations to be handled in the same way.
The Promise object has 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 (in progress), fulfilled (succeeded), and rejected (failed). Only the result of the asynchronous operation can determine the current state, and no other operation can change this state. This is also the origin of the name Promise -- its English meaning is "promise," indicating that nothing else can change it.
(2) Once the state changes, it will never change again, and this result can be obtained at any time. There are only two possible state changes for a Promise object: from pending to fulfilled, and from pending to rejected. Once either of these occurs, the state is frozen and will never change again -- it will always maintain that result, which is called resolved. If the change has already happened and you add a callback function to the Promise object, you will immediately get this result. This is completely different from events -- with events, if you miss one, listening for it again will not give you the result.
Note that for convenience in this chapter, resolved refers exclusively to the fulfilled state, not the rejected state.
With Promise objects, asynchronous operations can be expressed in a synchronous flow, avoiding deeply nested callback functions. Additionally, Promise objects provide a unified interface, making it easier to control asynchronous operations.
Promise also has some drawbacks. First, Promise cannot be canceled -- once created, it executes immediately and cannot be stopped midway. Second, if no callback function is set, errors thrown inside a Promise will not be reflected externally. Third, when in the pending state, there is no way to know the current progress (whether it just started or is about to complete).
If certain events occur repeatedly, using Stream (opens new window) is generally a better choice than deploying Promise.
# Basic Usage
ES6 specifies that the Promise object is a constructor used to create Promise instances.
The following code creates a Promise instance.
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* async operation succeeded */){
resolve(value);
} else {
reject(error);
}
});
2
3
4
5
6
7
8
9
The Promise constructor takes a function as its parameter, and that function's two parameters are resolve and reject, both functions provided by the JavaScript engine -- you don't need to implement them yourself.
The resolve function changes the Promise object's state from "pending" to "fulfilled" (from pending to resolved). It is called when the async operation succeeds and passes the result of the async operation as its argument. The reject function changes the Promise object's state from "pending" to "failed" (from pending to rejected). It is called when the async operation fails and passes the error from the async operation as its argument.
After a Promise instance is created, you can use the then method to specify callback functions for the resolved and rejected states.
promise.then(function(value) {
// success
}, function(error) {
// failure
});
2
3
4
5
The then method can accept two callback functions as parameters. The first is called when the Promise object's state becomes resolved, and the second is called when the state becomes rejected. The second function is optional and doesn't have to be provided. Both functions receive the value passed out by the Promise object as their parameter.
Here is a simple example of a Promise object.
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
2
3
4
5
6
7
8
9
In the code above, the timeout method returns a Promise instance representing a result that will occur after a period of time. After the specified time (ms parameter) has passed, the Promise instance's state changes to resolved, triggering the callback function bound by the then method.
A Promise executes immediately after being created.
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
2
3
4
5
6
7
8
9
10
11
12
13
14
In the code above, the Promise executes immediately upon creation, so Promise is output first. Then, the callback function specified by then will only execute after all synchronous tasks in the current script have completed, so resolved is output last.
Here is an example of asynchronous image loading.
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In the code above, Promise wraps an asynchronous image loading operation. If loading succeeds, resolve is called; otherwise, reject is called.
Here is an example of implementing an Ajax operation using a Promise object.
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('Error occurred', error);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
In the code above, getJSON is a wrapper around the XMLHttpRequest object for making HTTP requests for JSON data and returning a Promise object. Note that inside getJSON, both the resolve and reject functions are called with arguments.
If resolve and reject are called with arguments, those arguments are passed to the callback functions. The argument to reject is typically an instance of Error, representing a thrown error. The argument to resolve, besides a normal value, can also be another Promise instance, like the following.
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
2
3
4
5
6
7
8
In the code above, both p1 and p2 are Promise instances, but p2's resolve method takes p1 as its argument -- meaning one async operation's result is another async operation.
Note that p1's state is then passed to p2 -- in other words, p1's state determines p2's state. If p1's state is pending, then p2's callback function will wait for p1's state to change. If p1's state is already resolved or rejected, then p2's callback function will execute immediately.
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
2
3
4
5
6
7
8
9
10
11
12
In the code above, p1 is a Promise that becomes rejected after 3 seconds. p2's state changes after 1 second, with resolve returning p1. Since p2 returns another Promise, p2's own state becomes irrelevant -- p1's state determines p2's state. So the subsequent then statements all target p1. After another 2 seconds, p1 becomes rejected, triggering the callback function specified by the catch method.
Note that calling resolve or reject does not terminate the Promise's parameter function.
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
2
3
4
5
6
7
8
In the code above, after calling resolve(1), console.log(2) still executes and prints first. This is because an immediately resolved Promise executes at the end of the current event loop's microtask queue, always after the current loop's synchronous tasks.
Generally, once resolve or reject is called, the Promise's mission is complete. Subsequent operations should be placed in the then method, not directly after resolve or reject. So it's best to add a return statement before them to avoid unexpected behavior.
new Promise((resolve, reject) => {
return resolve(1);
// The following statement will not execute
console.log(2);
})
2
3
4
5
# Promise.prototype.then()
Promise instances have a then method, which means then is defined on the prototype object Promise.prototype. Its purpose is to add callback functions for when the Promise instance's state changes. As mentioned earlier, the first parameter of the then method is the callback for the resolved state, and the second parameter (optional) is the callback for the rejected state.
The then method returns a new Promise instance (note: not the original Promise instance). This allows chaining, where another then method is called after the first.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) { // post here is the return value from the previous then
// ...
});
2
3
4
5
The code above uses then to specify two callback functions in sequence. After the first callback completes, its return value is passed as the parameter to the second callback.
With chained thens, you can specify a set of callback functions to be called in order. In this case, the previous callback might return a Promise object (i.e., has an async operation), and the next callback will wait for that Promise object's state to change before being called.
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
2
3
4
5
6
7
In the code above, the first then's callback returns another Promise object. The second then's callback then waits for this new Promise object's state to change. If it becomes resolved, the first callback is called; if it becomes rejected, the second callback is called.
Using arrow functions, the code above can be written more concisely.
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
2
3
4
5
6
# Promise.prototype.catch()
The Promise.prototype.catch method is an alias for .then(null, rejection) or .then(undefined, rejection), used to specify a callback function for when an error occurs.
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// Handle errors from getJSON and the previous callback
console.log('An error occurred!', error);
});
2
3
4
5
6
In the code above, getJSON returns a Promise object. If the object's state becomes resolved, the callback specified by then is called. If the async operation throws an error, the state becomes rejected, and the callback specified by catch is called to handle the error. Also, if the callback specified by then throws an error during execution, it will also be caught by catch.
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// Equivalent to
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
2
3
4
5
6
Here is an example.
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
2
3
4
5
6
7
In the code above, promise throws an error that is caught by the callback specified by catch. Note that the code above is equivalent to the following two approaches.
// Approach 1
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// Approach 2
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Comparing the two approaches above, we can see that the reject method's effect is equivalent to throwing an error.
If the Promise state has already become resolved, throwing an error afterward is ineffective.
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
2
3
4
5
6
7
8
In the code above, the Promise throws an error after the resolve statement, but it is not caught because once the Promise's state changes, it stays that way permanently.
Promise object errors have a "bubbling" nature -- they propagate backward until caught. In other words, 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) {
// Handles errors from the three Promises above
});
2
3
4
5
6
7
In the code above, there are three Promise objects: one created by getJSON and two created by then. Any error thrown by any of them will be caught by the last catch.
In general, do not define the reject state callback function in the then method (i.e., then's second parameter). Always use the catch method instead.
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In the code above, the second approach is better than the first because it can catch errors from the then method's execution and is closer to the synchronous try/catch syntax. Therefore, always use the catch method instead of then's second parameter.
Unlike traditional try/catch blocks, if no catch callback is specified, errors thrown by a Promise object will not propagate to outer code -- there will be no response.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// The line below will throw an error because x is not declared
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined // Error but process doesn't exit
// 123
2
3
4
5
6
7
8
9
10
11
12
13
14
In the code above, the Promise object created by someAsyncThing has a syntax error. The browser prints the error message ReferenceError: x is not defined, but it doesn't exit the process or terminate script execution -- 123 is still output after 2 seconds. This means internal Promise errors don't affect code outside the Promise, colloquially known as "Promise swallows errors".
When this script runs on a server, the exit code is 0 (indicating successful execution). However, Node has an unhandledRejection event specifically for listening to uncaught reject errors. The script above would trigger this event's listener, where you can throw the error.
process.on('unhandledRejection', function (err, p) {
throw err;
});
2
3
In the code above, the unhandledRejection event listener has two parameters: the first is the error object, and the second is the Promise instance that reported the error, which can be used to understand the error context.
Note that Node plans to deprecate the unhandledRejection event in the future. If a Promise has uncaught internal errors, it will terminate the process directly, and the exit code will not be 0.
Here is another example.
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
2
3
4
5
6
7
In the code above, the Promise schedules an error to be thrown in the next "event loop." By that time, the Promise has already finished running, so this error is thrown outside the Promise function body and bubbles up to the outermost level, becoming an uncaught error.
It is always recommended to follow a Promise object with a catch method to handle internal errors. The catch method also returns a Promise object, so you can continue calling then after it.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// The line below will throw an error because x is not declared
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
After the catch callback runs, the then callback specified afterward will also run. If there is no error, the catch method is skipped.
Promise.resolve()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// carry on
2
3
4
5
6
7
8
The code above has no error, so the catch method is skipped and the then method is executed directly. If the then method throws an error at this point, it has nothing to do with the previous catch.
Errors can also be thrown inside a catch method.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// The line below will throw an error because x is not declared
resolve(x + 2);
});
};
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// The line below will throw an error because y is not declared
y + 2;
}).then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In the code above, the catch method throws an error. Since there is no subsequent catch method, this error is not caught and does not propagate externally. If rewritten, the result would be different.
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// The line below will throw an error because y is not declared
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
2
3
4
5
6
7
8
9
10
11
In the code above, the second catch method catches the error thrown by the first catch method.
# Promise.prototype.finally()
The finally method specifies an operation that will be executed regardless of the Promise object's final state. This method was introduced in the ES2018 standard.
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
2
3
4
In the code above, regardless of promise's final state, after the callback specified by then or catch executes, the callback specified by finally will execute.
Here is an example where a server uses Promise to handle a request and then uses finally to shut down the server.
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
2
3
4
5
The finally callback does not accept any arguments, which means there is no way to know whether the previous Promise's state was fulfilled or rejected. This indicates that operations inside finally should be state-independent and not rely on the Promise's execution result.
finally is essentially a special case of the then method.
promise
.finally(() => {
// statement
});
// Equivalent to
promise
.then(
result => {
// statement
return result;
},
error => {
// statement
throw error;
}
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In the code above, without the finally method, the same statements would need to be written for both the success and failure cases. With finally, they only need to be written once.
Its implementation is also simple.
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
2
3
4
5
6
7
In the code above, regardless of whether the previous Promise is fulfilled or rejected, the callback function callback will be executed.
From the implementation above, we can also see that finally always returns the original value.
// resolve value is undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve value is 2
Promise.resolve(2).finally(() => {})
// reject value is undefined
Promise.reject(3).then(() => {}, () => {})
// reject value is 3
Promise.reject(3).finally(() => {})
2
3
4
5
6
7
8
9
10
11
# Promise.all()
Promise.all() is used to wrap multiple Promise instances into a single new Promise instance.
const p = Promise.all([p1, p2, p3]);
In the code above, Promise.all() accepts an array as its parameter. p1, p2, and p3 are all Promise instances. If they are not, Promise.resolve (discussed below) is called first to convert the parameters to Promise instances before further processing. Also, the parameter to Promise.all() doesn't have to be an array, but it must have an Iterator interface, and each returned member must be a Promise instance.
The state of p is determined by p1, p2, and p3, and falls into two cases.
(1) Only when the states of p1, p2, and p3 all become fulfilled does p's state become fulfilled. At that point, the return values of p1, p2, and p3 form an array that is passed to p's callback.
(2) If any one of p1, p2, or p3 is rejected, p's state becomes rejected, and the return value of the first rejected instance is passed to p's callback.
Here is a concrete example.
// Generate an array of Promise objects
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
2
3
4
5
6
7
8
9
10
In the code above, promises is an array of 6 Promise instances. Only when all 6 instances become fulfilled, or when any one becomes rejected, will the callback after Promise.all be called.
Here is another example.
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
2
3
4
5
6
7
8
9
10
11
12
13
In the code above, booksPromise and userPromise are two async operations. Only when both return results will pickTopRecommendations be triggered.
Note that if a Promise instance that is a parameter has its own catch method, then when it is rejected, it will not trigger Promise.all()'s catch.
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('Error occurred');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: Error occurred]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In the code above, p1 resolves and p2 initially rejects. But p2 has its own catch method, which returns a new Promise instance. p2 actually points to this new instance. After this instance completes its catch, it also becomes resolved, causing both instances in Promise.all() to be resolved. Therefore, the then callback is called instead of the catch callback.
If p2 did not have its own catch method, Promise.all()'s catch would be called.
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('Error occurred');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: Error occurred
2
3
4
5
6
7
8
9
10
11
12
13
14
# Promise.race()
Promise.race() also wraps multiple Promise instances into a single new Promise instance.
const p = Promise.race([p1, p2, p3]);
In the code above, whichever of p1, p2, or p3 changes state first causes p's state to change accordingly. The return value of the first Promise instance to change is passed to p's callback.
The parameters of Promise.race() are the same as those of Promise.all(). If they are not Promise instances, Promise.resolve() is called first to convert them to Promise instances before further processing.
Here is an example: if no result is obtained within a specified time, the Promise's state changes to reject; otherwise, it changes to resolve.
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
2
3
4
5
6
7
8
9
10
In the code above, if the fetch method cannot return a result within 5 seconds, the state of variable p becomes rejected, triggering the callback specified by catch.
# Promise.allSettled()
The Promise.allSettled() method accepts a set of Promise instances as parameters and wraps them into a new Promise instance. The wrapping instance will only end when all parameter instances have returned results, regardless of whether they are fulfilled or rejected. This method was introduced in ES2020 (opens new window).
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
2
3
4
5
6
7
8
The code above sends three requests to the server. After all three requests complete, regardless of success or failure, the loading spinner disappears.
The new Promise instance returned by this method always ends with a fulfilled state, never rejected. Once the state becomes fulfilled, the Promise's listener receives an array, with each member corresponding to one of the Promise instances passed to Promise.allSettled().
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
2
3
4
5
6
7
8
9
10
11
12
In the code above, the return value allSettledPromise of Promise.allSettled() can only have a fulfilled state. Its listener receives the results array. Each member of the array is an object corresponding to the two Promise instances passed to Promise.allSettled(). Each object has a status property that can only be the string fulfilled or rejected. When fulfilled, the object has a value property; when rejected, it has a reason property, corresponding to the return values of the two states.
Here is an example of using the return value.
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// Filter out successful requests
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// Filter out failed requests and output reasons
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
2
3
4
5
6
7
8
9
10
Sometimes we don't care about the results of async operations -- we only care whether they have all finished. In this case, Promise.allSettled() is very useful. Without this method, ensuring all operations have finished would be cumbersome. Promise.all() cannot achieve this.
const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('All requests succeeded.');
} catch {
console.log('At least one request failed, and others may not have finished yet.');
}
2
3
4
5
6
7
8
9
In the code above, Promise.all() cannot determine whether all requests have finished. Achieving this goal is cumbersome to write, but with Promise.allSettled(), it becomes very easy.
# Promise.any()
Promise.any() accepts a set of Promise instances as parameters and wraps them into a new Promise instance. As soon as any parameter instance becomes fulfilled, the wrapping instance becomes fulfilled. Only when all parameter instances become rejected does the wrapping instance become rejected. This method was a Stage 3 proposal (opens new window) at the time of writing.
Promise.any() is very similar to Promise.race(), with one difference: it does not end just because a Promise becomes rejected.
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
2
3
4
5
6
7
8
9
10
11
In the code above, Promise.any()'s parameter array contains three Promise operations. As soon as any one becomes fulfilled, the Promise object returned by Promise.any() becomes fulfilled. Only if all three become rejected will the await command throw an error.
The error thrown by Promise.any() is not a regular error but an AggregateError instance. It acts like an array, with each member corresponding to an error thrown by a rejected operation. Here is an implementation example of AggregateError.
new AggregateError() extends Array -> AggregateError
const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;
2
3
4
5
6
To catch errors without using try...catch and await, you can write it like this.
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
2
3
4
5
6
7
8
Here is an example.
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
2
3
4
5
6
7
8
9
10
11
# Promise.resolve()
Sometimes you need to convert an existing object to a Promise object. The Promise.resolve() method serves this purpose.
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
The code above converts a jQuery deferred object into a new Promise object.
Promise.resolve() is equivalent to the following.
Promise.resolve('foo')
// Equivalent to
new Promise(resolve => resolve('foo'))
2
3
The Promise.resolve method's parameter falls into four cases.
(1) The parameter is a Promise instance
If the parameter is a Promise instance, Promise.resolve will return that instance unchanged.
(2) The parameter is a thenable object
A thenable object is an object with a then method, such as the following.
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
2
3
4
5
Promise.resolve will convert this object to a Promise object, and then immediately execute the thenable object's then method.
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
2
3
4
5
6
7
8
9
10
In the code above, after thenable's then method executes, object p1's state becomes resolved, immediately executing the callback specified by the last then, outputting 42.
(3) The parameter is not an object with a then method, or is not an object at all
If the parameter is a primitive value or an object without a then method, Promise.resolve returns a new Promise object with a resolved state.
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
2
3
4
5
6
The code above creates a new Promise object instance p. Since the string Hello is not an async operation (determined by the fact that the string object has no then method), the returned Promise instance's state is resolved from the moment it is created, so the callback executes immediately. The parameter of Promise.resolve is also passed to the callback.
(4) No parameter
Promise.resolve() can be called without any parameter, directly returning a resolved state Promise object.
So, if you want a Promise object, a convenient approach is to call Promise.resolve() directly.
const p = Promise.resolve();
p.then(function () {
// ...
});
2
3
4
5
The variable p in the code above is a Promise object.
Note that an immediately resolve()d Promise object executes at the end of the current "event loop," not at the beginning of the next one.
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
2
3
4
5
6
7
8
9
10
11
12
13
In the code above, setTimeout(fn, 0) executes at the beginning of the next "event loop," Promise.resolve() executes at the end of the current "event loop," and console.log('one') executes immediately, so it is output first.
# Promise.reject()
The Promise.reject(reason) method also returns a new Promise instance with a rejected state.
const p = Promise.reject('Error occurred');
// Equivalent to
const p = new Promise((resolve, reject) => reject('Error occurred'))
p.then(null, function (s) {
console.log(s)
});
// Error occurred
2
3
4
5
6
7
8
The code above creates a Promise object instance p with a rejected state, and the callback executes immediately.
Note that Promise.reject()'s argument is passed as-is as the reject reason to subsequent methods. This is different from Promise.resolve.
const thenable = {
then(resolve, reject) {
reject('Error occurred');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
2
3
4
5
6
7
8
9
10
11
In the code above, Promise.reject's argument is a thenable object. After execution, the catch method's parameter is not the string "Error occurred" thrown by reject, but the thenable object itself.
# Applications
# Loading Images
We can write image loading as a Promise. Once loading is complete, the Promise's state changes.
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
2
3
4
5
6
7
8
# Combining Generator Functions with Promise
When using Generator functions to manage flow, encountering async operations typically returns a Promise object.
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
In the code above, the Generator function g contains an async operation getFoo that returns a Promise object. The run function handles this Promise object and calls the next next method.
# Promise.try()
In real-world development, a common situation arises: you don't know or don't want to distinguish whether a function f is synchronous or asynchronous, but you want to use Promise to handle it. This way, regardless of whether f contains async operations, you can use then to specify the next step and catch to handle errors thrown by f. The typical approach would be:
Promise.resolve().then(f)
The drawback of this approach is that if f is synchronous, it will execute at the end of the current event loop.
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
2
3
4
5
In the code above, function f is synchronous, but wrapping it in a Promise makes it execute asynchronously.
Is there a way to make synchronous functions execute synchronously and asynchronous functions execute asynchronously, while giving them a unified API? The answer is yes, and there are two approaches. The first is using async functions.
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
2
3
4
5
In the code above, the second line is an immediately invoked anonymous function that immediately executes the async function inside. Therefore, if f is synchronous, it will produce a synchronous result; if f is asynchronous, you can specify the next step with then, as shown below.
(async () => f())()
.then(...)
2
Note that async () => f() will swallow errors thrown by f(). So, if you want to catch errors, you should use promise.catch.
(async () => f())()
.then(...)
.catch(...)
2
3
The second approach is using new Promise().
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
2
3
4
5
6
7
8
9
The code above also uses an immediately invoked anonymous function to execute new Promise(). In this case, synchronous functions also execute synchronously.
Given that this is a very common need, there is now a proposal (opens new window) to provide a Promise.try method to replace the approaches above.
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
2
3
4
5
In fact, Promise.try has existed for a long time. Promise libraries Bluebird (opens new window), Q (opens new window), and when (opens new window) have long provided this method.
Since Promise.try provides a unified handling mechanism for all operations, if you want to use then to manage flow, it's best to wrap everything with Promise.try. This has many benefits (opens new window), one of which is better exception management.
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
2
3
4
5
6
In the code above, database.users.get() returns a Promise object. If it throws an async error, it can be caught with catch, like the following.
database.users.get({id: userId})
.then(...)
.catch(...)
2
3
But database.users.get() might also throw a synchronous error (such as a database connection error, depending on the implementation), in which case you'd have to use try...catch to catch it.
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
2
3
4
5
6
7
This kind of writing is quite clumsy. In this case, you can use promise.catch() to catch all synchronous and asynchronous errors uniformly.
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
2
3
In fact, Promise.try simulates a try block, just as promise.catch simulates a catch block.