Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3de63df

Browse files
committed
promise.all task
1 parent 3d7abb9 commit 3de63df

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
The problem is that `Promise.all` immediately rejects when one of its promises rejects. In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.
3+
4+
Meanwhile, even if one of the queries fails, other promises are *not affected* - they independently continue their execution. In our case, the third query throws an error of its own after a bit of time. And that error is never caught. We can see it in the console.
5+
6+
The problem is especially dangerous in server-side environments, such as Node.js, when an uncaught error may cause the process to crash.
7+
8+
How to fix it?
9+
10+
A natural solution would be to cancel all unfinished queries when one of them fails. This way we avoid any potential errors.
11+
12+
However, the bad news is that service calls (such as `database.query`) are often implemented by a 3rd-party library which doesn't support cancellation. So there's usually no way to cancel a call.
13+
14+
Instead we can write our own wrapper function around `Promise.all` which adds a custom `then/catch` handler to each promise to track them: results are gathered and, if an error occurs, all subsequent promises are ignored.
15+
16+
```js
17+
function customPromiseAll(promises) {
18+
return new Promise((resolve, reject) => {
19+
const results = [];
20+
let resultsCount = 0;
21+
let hasError = false; // we'll set it to true upon first error
22+
23+
promises.forEach((promise, index) => {
24+
promise
25+
.then(result => {
26+
if (hasError) return; // ignore the promise if already errored
27+
results[index] = result;
28+
resultsCount++;
29+
if (resultsCount === promises.length) {
30+
resolve(results); // when all results are ready - successs
31+
}
32+
})
33+
.catch(error => {
34+
if (hasError) return; // ignore the promise if already errored
35+
hasError = true; // wops, error!
36+
reject(error); // fail with rejection
37+
});
38+
});
39+
});
40+
}
41+
```
42+
43+
This approach has an issue of its own - it's often undesirable to `disconnect()` when queries are still in the process.
44+
45+
It may be important that all queries complete, especially if some of them make important updates.
46+
47+
So we should wait until all promises are settled before going further with the execution and eventually disconnecting.
48+
49+
Here's one more implementation. It also resolves with the first error, but waits until all promises are settled.
50+
51+
```js
52+
function customPromiseAllWait(promises) {
53+
return new Promise((resolve, reject) => {
54+
const results = new Array(promises.length);
55+
let settledCount = 0;
56+
let firstError = null;
57+
58+
promises.forEach((promise, index) => {
59+
Promise.resolve(promise)
60+
.then(result => {
61+
results[index] = result;
62+
})
63+
.catch(error => {
64+
if (firstError === null) {
65+
firstError = error;
66+
}
67+
})
68+
.finally(() => {
69+
settledCount++;
70+
if (settledCount === promises.length) {
71+
if (firstError !== null) {
72+
reject(firstError);
73+
} else {
74+
resolve(results);
75+
}
76+
}
77+
});
78+
});
79+
});
80+
}
81+
```
82+
83+
Now `await customPromiseAllWait(...)` will stall the execution until all queries are processed.
84+
85+
This is the most reliable approach.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
# Dangerous Promise.all
3+
4+
`Promise.all` is a great way to parallelize multiple operations. It's especially useful when we need to make parallel requests to multiple services.
5+
6+
However, there's a hidden danger. Hopefully we'll be able to identify its cause.
7+
8+
Let's say we have a connection to a remote service, such as a database.
9+
10+
There're two functions: `connect()` and `disconnect()`.
11+
12+
When connected, we can send requests using `database.query(...)` - an async function which usually returns the result but also may throw an error.
13+
14+
Here's a simple implementation:
15+
16+
```js
17+
let database;
18+
19+
function connect() {
20+
database = {
21+
async query(isOk) {
22+
if (!isOk) throw new Error('Query failed');
23+
}
24+
};
25+
}
26+
27+
function disconnect() {
28+
database = null;
29+
}
30+
31+
// intended usage:
32+
// connect()
33+
// ...
34+
// database.query(true) to emulate a successful call
35+
// database.query(false) to emulate a failed call
36+
// ...
37+
// disconnect()
38+
```
39+
40+
Now here's the problem.
41+
42+
We write a simple code to connect and send 3 queries in parallel (all of them take different time, e.g. 100, 200 and 300ms), then disconnect:
43+
44+
```js
45+
// Helper function to call async function fn after ms milliseconds
46+
function delay(fn, ms) {
47+
return new Promise((resolve, reject) => {
48+
setTimeout(() => fn().then(resolve, reject), ms);
49+
});
50+
}
51+
52+
async function run() {
53+
connect();
54+
55+
try {
56+
await Promise.all([
57+
// these 3 parallel jobs take different time: 100, 200 and 300 ms
58+
delay(() => database.query(true), 100),
59+
delay(() => database.query(false), 200),
60+
delay(() => database.query(false), 300)
61+
]);
62+
} catch(error) {
63+
console.log('Error handled (or was it?)');
64+
}
65+
66+
disconnect();
67+
}
68+
69+
run();
70+
```
71+
72+
Two of these queries are (by chance) unsuccessful, but we're smart enough to wrap the `Promise.all` call into a `try...catch` block.
73+
74+
However, this script actually leads to an uncaught error in console!
75+
76+
Why? How to avoid it?

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /