Promise.allSettled
This commit is contained in:
parent
027933531e
commit
d7f90176c7
16 changed files with 191 additions and 253 deletions
|
@ -1,6 +1,6 @@
|
|||
# Promise API
|
||||
|
||||
There are 4 static methods in the `Promise` class. We'll quickly cover their use cases here.
|
||||
There are 5 static methods in the `Promise` class. We'll quickly cover their use cases here.
|
||||
|
||||
## Promise.resolve
|
||||
|
||||
|
@ -102,7 +102,7 @@ let urls = [
|
|||
'https://api.github.com/users/jeresig'
|
||||
];
|
||||
|
||||
// map every url to the promise fetch(github url)
|
||||
// map every url to the promise of the fetch
|
||||
let requests = urls.map(url => fetch(url));
|
||||
|
||||
// Promise.all waits until all jobs are resolved
|
||||
|
@ -112,7 +112,7 @@ Promise.all(requests)
|
|||
));
|
||||
```
|
||||
|
||||
A more real-life example with fetching user information for an array of github users by their names (or we could fetch an array of goods by their ids, the logic is same):
|
||||
A bigger example with fetching user information for an array of github users by their names (or we could fetch an array of goods by their ids, the logic is same):
|
||||
|
||||
```js run
|
||||
let names = ['iliakan', 'remy', 'jeresig'];
|
||||
|
@ -134,7 +134,7 @@ Promise.all(requests)
|
|||
.then(users => users.forEach(user => alert(user.name)));
|
||||
```
|
||||
|
||||
If any of the promises is rejected, `Promise.all` immediately rejects with that error.
|
||||
**If any of the promises is rejected, `Promise.all` immediately rejects with that error.**
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -150,9 +150,13 @@ Promise.all([
|
|||
|
||||
Here the second promise rejects in two seconds. That leads to immediate rejection of `Promise.all`, so `.catch` executes: the rejection error becomes the outcome of the whole `Promise.all`.
|
||||
|
||||
The important detail is that promises provide no way to "cancel" or "abort" their execution. So other promises continue to execute, and then eventually settle, but all their results are ignored.
|
||||
```warn header="In case of an error, other promises are ignored"
|
||||
If one promise rejects, `Promise.all` immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.
|
||||
|
||||
There are ways to avoid this: we can either write additional code to `clearTimeout` (or otherwise cancel) the promises in case of an error, or we can make errors show up as members in the resulting array (see the task below this chapter about it).
|
||||
For example, if there are multiple `fetch` calls, like in the example above, and one fails, other ones will still continue to execute, but `Promise.all` don't watch them any more. They will probably settle, but the result will be ignored.
|
||||
|
||||
`Promise.all` does nothing to cancel them, as there's no concept of "cancellation" in promises. In [another chapter](fetch-abort) we'll cover `AbortController` that aims to help with that, but it's not a part of the Promise API.
|
||||
```
|
||||
|
||||
````smart header="`Promise.all(...)` allows non-promise items in `iterable`"
|
||||
Normally, `Promise.all(...)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`.
|
||||
|
@ -173,6 +177,84 @@ So we are able to pass non-promise values to `Promise.all` where convenient.
|
|||
|
||||
````
|
||||
|
||||
## Promise.allSettled
|
||||
|
||||
[recent browser="new"]
|
||||
|
||||
`Promise.all` rejects as a whole if any promise rejects. That's good in cases, when we need *all* results to go on:
|
||||
|
||||
```js
|
||||
Promise.all([
|
||||
fetch('/template.html'),
|
||||
fetch('/style.css'),
|
||||
fetch('/data.json')
|
||||
]).then(render); // render method needs them all
|
||||
```
|
||||
|
||||
`Promise.allSettled` waits for all promises to settle: even if one rejects, it waits for the others. The resulting array has:
|
||||
|
||||
- `{status:"fulfilled", value:result}` for successful responses,
|
||||
- `{status:"rejected", reason:error}` for errors.
|
||||
|
||||
For example, we'd like to fetch the information about multiple users. Even if one request fails, we're interested in the others.
|
||||
|
||||
Let's use `Promise.allSettled`:
|
||||
|
||||
```js run
|
||||
let urls = [
|
||||
'https://api.github.com/users/iliakan',
|
||||
'https://api.github.com/users/remy',
|
||||
'https://no-such-url'
|
||||
];
|
||||
|
||||
Promise.allSettled(urls.map(url => fetch(url)))
|
||||
.then(results => { // (*)
|
||||
results.forEach((result, num) => {
|
||||
if (result.status == "fulfilled") {
|
||||
alert(`${urls[num]}: ${result.value.status}`);
|
||||
}
|
||||
if (result.status == "rejected") {
|
||||
alert(`${urls[num]}: ${result.reason}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The `results` in the line `(*)` above will be:
|
||||
```js
|
||||
[
|
||||
{status: 'fulfilled', value: ...response...},
|
||||
{status: 'fulfilled', value: ...response...},
|
||||
{status: 'rejected', reason: ...error object...}
|
||||
]
|
||||
```
|
||||
|
||||
So, for each promise we get its status and `value/reason`.
|
||||
|
||||
### Polyfill
|
||||
|
||||
If the browser doesn't support `Promise.allSettled`, it's easy to polyfill:
|
||||
|
||||
```js
|
||||
if(!Promise.allSettled) {
|
||||
Promise.allSettled = function(promises) {
|
||||
return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
|
||||
state: 'fulfilled',
|
||||
value: v,
|
||||
}), r => ({
|
||||
state: 'rejected',
|
||||
reason: r,
|
||||
}))));
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
In this code, `promises.map` takes input values, turns into promises (just in case a non-promise was passed) with `p => Promise.resolve(p)`, and then adds `.then` handler to it.
|
||||
|
||||
That handler turns a successful result `v` into `{state:'fulfilled', value:v}`, and an error `r` into `{state:'rejected', reason:r}`. That's exactly the format of `Promise.allSettled`.
|
||||
|
||||
Then we can use `Promise.allSettled` to get the results or *all* given promises, even if some of them reject.
|
||||
|
||||
## Promise.race
|
||||
|
||||
Similar to `Promise.all`, it takes an iterable of promises, but instead of waiting for all of them to finish, it waits for the first result (or error), and goes on with it.
|
||||
|
@ -197,11 +279,14 @@ So, the first result/error becomes the result of the whole `Promise.race`. After
|
|||
|
||||
## Summary
|
||||
|
||||
There are 4 static methods of `Promise` class:
|
||||
There are 5 static methods of `Promise` class:
|
||||
|
||||
1. `Promise.resolve(value)` -- makes a resolved promise with the given value.
|
||||
2. `Promise.reject(error)` -- makes a rejected promise with the given error.
|
||||
3. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored.
|
||||
4. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
|
||||
4. `Promise.allSettled(promises)` (a new method) -- waits for all promises to resolve or reject and returns an array of their results as object with:
|
||||
- `state`: `'fulfilled'` or `'rejected'`
|
||||
- `value` (if fulfilled) or `reason` (if rejected).
|
||||
5. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome.
|
||||
|
||||
Of these four, `Promise.all` is the most common in practice.
|
||||
Of these five, `Promise.all/allSettled` are the most common in practice.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue