diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index a9183c80..af38241e 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -25,15 +25,16 @@ loadScript('/my/script.js'); The function is called "asynchronously," because the action (script loading) finishes not now, but later. -The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too. +If there's a code below `loadScript(…)`, it doesn't wait until the loading finishes. ```js loadScript('/my/script.js'); -// the code below loadScript doesn't wait for the script loading to finish +// the code below loadScript +// doesn't wait for the script loading to finish // ... ``` -Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them. +We'd like to use the new script as soon as it loads. It declares new functions, and we want to run them. But if we do that immediately after the `loadScript(…)` call, that wouldn't work: @@ -45,7 +46,7 @@ newFunction(); // no such function! */!* ``` -Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script. +Naturally, the browser probably didn't have time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script. Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads: diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index 4dae2aaa..09d148dd 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -2,7 +2,7 @@ Imagine that you're a top singer, and fans ask day and night for your upcoming single. -To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified. +To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, fire in the studio, so that you can't publish the song, they will still be notified. Everyone is happy, because the people don't crowd you anymore, and fans, because they won't miss the single. @@ -22,27 +22,29 @@ let promise = new Promise(function(resolve, reject) { }); ``` -The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer". +The function passed to `new Promise` is called the *executor*. When the promise is created, it runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer". -The resulting `promise` object has internal properties: +Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself. Our code is only inside executor. -- `state` — initially "pending", then changes to either "fulfilled" or "rejected", -- `result` — an arbitrary value, initially `undefined`. +When the executor obtains the result, be it soon or late - doesn't matter, it should call one of these callbacks: -When the executor finishes the job, it should call one of the functions that it gets as arguments: +- `resolve(value)` — if the job finished successfully, with result `value`. +- `reject(error)` — if an error occurred, `error` is the error object. -- `resolve(value)` — to indicate that the job finished successfully: - - sets `state` to `"fulfilled"`, - - sets `result` to `value`. -- `reject(error)` — to indicate that an error occurred: - - sets `state` to `"rejected"`, - - sets `result` to `error`. +So to summarize: the executor runs automatically, it should do a job and then call either `resolve` or `reject`. + +The `promise` object returned by `new Promise` constructor has internal properties: + +- `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called. +- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called. + +So the executor moves `promise` to one of these states: ![](promise-resolve-reject.svg) -Later we'll see how these changes become known to "fans". +Later we'll see how "fans" can subscribe to these changes. -Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`): +Here's an example of a Promise constructor and a simple executor function with delayed "producing code" (via `setTimeout`): ```js run let promise = new Promise(function(resolve, reject) { @@ -86,7 +88,9 @@ All further calls of `resolve` and `reject` are ignored: ```js let promise = new Promise(function(resolve, reject) { +*!* resolve("done"); +*/!* reject(new Error("…")); // ignored setTimeout(() => resolve("…")); // ignored @@ -99,7 +103,7 @@ Also, `resolve`/`reject` expect only one argument (or none) and will ignore addi ```` ```smart header="Reject with `Error` objects" -In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. +In case something goes wrong, we must call `reject`. That can be done with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. ``` ````smart header="Immediately calling `resolve`/`reject`" @@ -112,13 +116,13 @@ let promise = new Promise(function(resolve, reject) { }); ``` -For instance, this might happen when we start to do a job but then see that everything has already been completed and cached. +For instance, this might happen when we start to do a job but then see that everything has already been completed and cached. That's fine. We immediately have a resolved promise. ```` ```smart header="The `state` and `result` are internal" -The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below. +The properties `state` and `result` of the Promise object are internal. We can't directly access them. We can use the methods `.then`/`.catch`/`.finally` for that. They are described below. ``` ## Consumers: then, catch, finally @@ -138,15 +142,9 @@ promise.then( ); ``` -The first argument of `.then` is a function that: +The first argument of `.then` is a function that runs when the promise is resolved, and receives the result. -1. runs when the promise is resolved, and -2. receives the result. - -The second argument of `.then` is a function that: - -1. runs when the promise is rejected, and -2. receives the error. +The second argument of `.then` is a function that runs when the promise is rejected, and receives the error. For instance, here's a reaction to a successfully resolved promise: @@ -216,7 +214,7 @@ The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a short Just like there's a `finally` clause in a regular `try {...} catch {...}`, there's `finally` in promises. -The call `.finally(f)` is similar to `.then(f, f)` in the sense that it always runs when the promise is settled: be it resolve or reject. +The call `.finally(f)` is similar to `.then(f, f)` in the sense that `f` always runs when the promise is settled: be it resolve or reject. `finally` is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is. @@ -264,7 +262,7 @@ It's not exactly an alias of `then(f,f)` though. There are several important dif 3. Last, but not least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function `f`. ````smart header="On settled promises handlers runs immediately" -If a promise is pending, `.then/catch/finally` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately: +If a promise is pending, `.then/catch/finally` handlers wait for it. Otherwise, if a promise has already settled, they execute immediately: ```js run // an immediately resolved promise @@ -272,8 +270,6 @@ let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done! (shows up right now) ``` - -The good thing is: a `.then` handler is guaranteed to run whether the promise takes time or settles it immediately. ```` Next, let's see more practical examples of how promises can help us to write asynchronous code. @@ -324,7 +320,7 @@ promise.then( error => alert(`Error: ${error.message}`) ); -promise.then(script => alert('One more handler to do something else!')); +promise.then(script => alert('Another handler...')); ``` We can immediately see a few benefits over the callback-based pattern: diff --git a/1-js/11-async/02-promise-basics/promise-reject-1.svg b/1-js/11-async/02-promise-basics/promise-reject-1.svg index 822ccb2e..0c81c410 100644 --- a/1-js/11-async/02-promise-basics/promise-reject-1.svg +++ b/1-js/11-async/02-promise-basics/promise-reject-1.svg @@ -1,5 +1,5 @@ - + promise-reject-1.svg Created with sketchtool. @@ -10,18 +10,18 @@ new Promise(executor) - state: "pending" - result: undefined + state: "pending" + result: undefined - - reject(error) + reject(error) - + - state: "rejected" - result: error + state: "rejected" + result: error + \ No newline at end of file diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1.svg b/1-js/11-async/02-promise-basics/promise-resolve-1.svg index 6d8d2781..6f28e22c 100644 --- a/1-js/11-async/02-promise-basics/promise-resolve-1.svg +++ b/1-js/11-async/02-promise-basics/promise-resolve-1.svg @@ -1,26 +1,26 @@ - + promise-resolve-1.svg Created with sketchtool. - + - new Promise(executor) + new Promise(executor) - state: "pending" - result: undefined + state: "pending" + result: undefined - + - resolve("done") + resolve("done") - + - state: "fulfilled" - result: "done" + state: "fulfilled" + result: "done" diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject.svg b/1-js/11-async/02-promise-basics/promise-resolve-reject.svg index 94173351..c761c4c0 100644 --- a/1-js/11-async/02-promise-basics/promise-resolve-reject.svg +++ b/1-js/11-async/02-promise-basics/promise-resolve-reject.svg @@ -5,13 +5,13 @@ Created with sketchtool. - + - new Promise(executor) + new Promise(executor) - state: "pending" - result: undefined + state: "pending" + result: undefined diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index ae646ba5..6240a6b2 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -42,7 +42,7 @@ So far, quite simple, right? Two more details: 1. Rendering never happens while the engine executes a task. Doesn't matter if the task takes a long time. Changes to DOM are painted only after the task is complete. -2. If a task takes too long, the browser can't do other tasks, process user events, so after a time it raises an alert like "Page Unresponsive" and suggesting to kill the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to infinite loop. +2. If a task takes too long, the browser can't do other tasks, process user events, so after a time it raises an alert like "Page Unresponsive" suggesting to kill the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to infinite loop. That was a theory. Now let's see how we can apply that knowledge. @@ -52,7 +52,7 @@ Let's say we have a CPU-hungry task. For example, syntax-highlighting (used to colorize code examples on this page) is quite CPU-heavy. To highlight the code, it performs the analysis, creates many colored elements, adds them to the document -- for a big text that takes a lot of time. -While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hang" for a bit, which is unacceptable. +While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hiccup" or even "hang" for a bit, which is unacceptable. We can evade problems by splitting the big task into pieces. Highlight first 100 lines, then schedule `setTimeout` (with zero-delay) another 100 lines, and so on. @@ -269,9 +269,9 @@ The richer event loop picture looks like this: **All microtasks are completed before any other event handling or rendering or any other macrotask takes place.** -That's important, as it guarantees that the application environment be basically the same (no mouse coordinate changes, no new network data, etc) between microtasks. +That's important, as it guarantees that the application environment is basically the same (no mouse coordinate changes, no new network data, etc) between microtasks. -If we'd like to execute a function asynchronously (after the current code), but before changes are rendered or new events, we can schedule it with `queueMicrotask`. +If we'd like to execute a function asynchronously (after the current code), but before changes are rendered or new events handled, we can schedule it with `queueMicrotask`. Here's an example with "counting progress bar", similar to the one shown previously, but `queueMicrotask` is used instead of `setTimeout`. You can see that it renders at the very end. Just like the synchronous code: diff --git a/figures.sketch b/figures.sketch index 6b935c3c..0c29b354 100644 Binary files a/figures.sketch and b/figures.sketch differ