Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1fce745

Browse files
authoredJun 1, 2025
Merge pull request #55 from javascript-tutorial/master
merge 1.6.2025
2 parents c2e69cb + ca57c28 commit 1fce745

File tree

12 files changed

+198
-6
lines changed

12 files changed

+198
-6
lines changed
 

‎1-js/01-getting-started/4-devtools/article.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Otevřou se vývojářské nástroje na záložce „Console“.
2222

2323
Budou vypadat zhruba takto:
2424

25-
![chrome](chrome.png)
25+
![chrome](chrome.webp)
2626

2727
Vzhled vývojářských nástrojů závisí na vaší verzi Chrome. Čas od času se mění, ale měl by se podobat obrázku.
2828

@@ -49,8 +49,8 @@ Jejich vzhled je vcelku podobný. Až budete vědět, jak používat nástroje v
4949

5050
Safari (prohlížeč pro Mac, není podporován ve Windows nebo Linuxu) je v tomto ohledu trochu zvláštní. Nejprve musíme povolit „menu Vývoj“.
5151

52-
Otevřete „Preferences“ a jděte na záložku „Advanced“. Dole uvidíte checkbox, který zaškrtněte:
53-
52+
Otevřete „Settings“ a jděte na záložku „Advanced“. Dole uvidíte checkbox, který zaškrtněte:
53+
5454
![safari](safari.png)
5555

5656
Nyní můžete zapínat konzoli pomocí `key:Cmd+Opt+C`. Všimněte si také, že v horním menu se objevila nová položka s názvem „Develop“ („Vývoj“), která obsahuje mnoho příkazů a nastavení.
-41.1 KB
Binary file not shown.
22.2 KB
Binary file not shown.
48.3 KB
Binary file not shown.
-67.8 KB
Binary file not shown.
83 KB
Loading
Loading

‎1-js/06-advanced-functions/06-function-object/article.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works)
326326

327327
Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function.
328328

329-
The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to can call itself reliably.
329+
The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably.
330330

331331
```smart header="There's no such thing for Function Declaration"
332332
The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name.

‎1-js/06-advanced-functions/10-bind/article.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ funcUser(); // John
125125
*/!*
126126
```
127127

128-
Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`.
128+
Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`.
129129

130130
All arguments are passed to the original `func` "as is", for instance:
131131

‎1-js/10-error-handling/1-try-catch/article.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ For instance:
632632
633633
The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers.
634634
635-
There are also web-services that provide error-logging for such cases, like <https://errorception.com> or <https://www.muscula.com>.
635+
There are also web-services that provide error-logging for such cases, like <https://muscula.com> or <https://www.sentry.io>.
636636
637637
They work like this:
638638
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
The root of the problem is that `Promise.all` immediately rejects when one of its promises rejects, but it do nothing to cancel the other promises.
3+
4+
In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.Meanwhile, 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+
An ideal 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. Then there's no way to cancel a call.
13+
14+
As an alternative, 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 another implementation. It behaves similar to `Promise.all` - 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 a more reliable approach, as it guarantees a predictable execution flow.
86+
87+
Lastly, if we'd like to process all errors, we can use either use `Promise.allSettled` or write a wrapper around it to gathers all errors in a single [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) object and rejects with it.
88+
89+
```js
90+
// wait for all promises to settle
91+
// return results if no errors
92+
// throw AggregateError with all errors if any
93+
function allOrAggregateError(promises) {
94+
return Promise.allSettled(promises).then(results => {
95+
const errors = [];
96+
const values = [];
97+
98+
results.forEach((res, i) => {
99+
if (res.status === 'fulfilled') {
100+
values[i] = res.value;
101+
} else {
102+
errors.push(res.reason);
103+
}
104+
});
105+
106+
if (errors.length > 0) {
107+
throw new AggregateError(errors, 'One or more promises failed');
108+
}
109+
110+
return values;
111+
});
112+
}
113+
```
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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. We'll see an example in this task and explore how to avoid it.
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 wrote the 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+
// we use the `delay` helper to achieve this effect
59+
*!*
60+
delay(() => database.query(true), 100),
61+
delay(() => database.query(false), 200),
62+
delay(() => database.query(false), 300)
63+
*/!*
64+
]);
65+
} catch(error) {
66+
console.log('Error handled (or was it?)');
67+
}
68+
69+
disconnect();
70+
}
71+
72+
run();
73+
```
74+
75+
Two of these queries happen to be unsuccessful, but we're smart enough to wrap the `Promise.all` call into a `try..catch` block.
76+
77+
However, this doesn't help! This script actually leads to an uncaught error in console!
78+
79+
Why? How to avoid it?

0 commit comments

Comments
 (0)
Please sign in to comment.