refactor promise, geneerators, global object

This commit is contained in:
Ilya Kantor 2019-02-28 00:56:39 +03:00
parent be9c5a7b5f
commit 2ee2751216
69 changed files with 900 additions and 643 deletions

View file

@ -214,7 +214,7 @@ sayHi(); // undefined
In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error.
In non-strict mode (if one forgets `use strict`) the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later). This is a historical behavior that `"use strict"` fixes.
In non-strict mode the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later in the chapter [](info:global-object)). This is a historical behavior that `"use strict"` fixes.
Please note that usually a call of a function that uses `this` without an object is not normal, but rather a programming mistake. If a function has `this`, then it is usually meant to be called in the context of an object.

View file

@ -70,7 +70,7 @@ The Lexical Environment object consists of two parts:
1. *Environment Record* -- an object that has all local variables as its properties (and some other information like the value of `this`).
2. A reference to the *outer lexical environment*, usually the one associated with the code lexically right outside of it (outside of the current curly brackets).
So, a "variable" is just a property of the special internal object, Environment Record. "To get or change a variable" means "to get or change a property of the Lexical Environment".
**So, a "variable" is just a property of the special internal object, Environment Record. "To get or change a variable" means "to get or change a property of that object".**
For instance, in this simple code, there is only one Lexical Environment:
@ -100,7 +100,11 @@ To summarize:
### Function Declaration
Function Declarations are special. Unlike `let` variables, they are processed not when the execution reaches them, but when a Lexical Environment is created. For the global Lexical Environment, it means the moment when the script is started.
Till now, we only observed variables. Now enter Function Declarations.
**Unlike `let` variables, they are fully initialized not when the execution reaches them, but earlier, when a Lexical Environment is created.**
For top-level functions, it means the moment when the script is started.
That is why we can call a function declaration before it is defined.
@ -111,10 +115,14 @@ The code below demonstrates that the Lexical Environment is non-empty from the b
### Inner and outer Lexical Environment
During the call, `say()` uses an outer variable, so let's look at the details of what's going on.
Now let's go on and explore what happens when a function accesses an outer variable.
During the call, `say()` uses the outer variable `phrase`, let's look at the details of what's going on.
First, when a function runs, a new function Lexical Environment is created automatically. That's a general rule for all functions. That Lexical Environment is used to store local variables and parameters of the call.
For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow):
<!--
```js
let phrase = "Hello";
@ -126,25 +134,27 @@ First, when a function runs, a new function Lexical Environment is created autom
say("John"); // Hello, John
```-->
Here's the picture of Lexical Environments when the execution is inside `say("John")`, at the line labelled with an arrow:
![lexical environment](lexical-environment-simple.png)
During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):
So, during the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global):
- The inner Lexical Environment corresponds to the current execution of `say`. It has a single variable: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
- The inner Lexical Environment corresponds to the current execution of `say`.
It has a single variable: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`.
- The outer Lexical Environment is the global Lexical Environment.
The inner Lexical Environment has the `outer` reference to the outer one.
It has `phrase` and the function itself.
**When code wants to access a variable -- it is first searched for in the inner Lexical Environment, then in the outer one, then the more outer one and so on until the end of the chain.**
The inner Lexical Environment has a reference to the outer one.
**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the end of the chain.**
If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to an undefined variable creates a new global variable, for backwards compatibility.
Let's see how the search proceeds in our example:
- When the `alert` inside `say` wants to access `name`, it finds it immediately in the function Lexical Environment.
- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the `outer` reference and finds it globally.
- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the enclosing Lexical Environment and finds it there.
![lexical environment lookup](lexical-environment-simple-lookup.png)
@ -211,11 +221,11 @@ function sayHiBye(firstName, lastName) {
}
```
Here the *nested* function `getFullName()` is made for convenience. It can access the outer variables and so can return the full name.
Here the *nested* function `getFullName()` is made for convenience. It can access the outer variables and so can return the full name. Nested functions are quite common in Javascript.
What's more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.
What's much more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables.
An example with the constructor function (see the chapter <info:constructor-new>):
For instance, here the nested function is assigned to the new object by the [constructor function](info:constructor-new):
```js run
// constructor function returns a new object
@ -228,10 +238,10 @@ function User(name) {
}
let user = new User("John");
user.sayHi(); // the method code has access to the outer "name"
user.sayHi(); // the method "sayHi" code has access to the outer "name"
```
An example with returning a function:
And here we just create and return a "counting" function:
```js run
function makeCounter() {
@ -249,7 +259,7 @@ alert( counter() ); // 1
alert( counter() ); // 2
```
Let's go on with the `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator), and more. So the example is not as artificial as it may appear.
Let's go on with the `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator), and more.
How does the counter work internally?
@ -303,9 +313,11 @@ Hopefully, the situation with outer variables is quite clear for you now. But in
## Environments in detail
Now that you understand how closures work generally, we can descend to the very nuts and bolts.
Now that you understand how closures work generally, that's already very good.
Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you understand everything. Please note the additional `[[Environment]]` property that we didn't cover yet.
Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you know things in the very detail.
Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity.
1. When the script has just started, there is only global Lexical Environment:
@ -313,7 +325,7 @@ Here's what's going on in the `makeCounter` example step-by-step, follow it to m
At that starting moment there is only `makeCounter` function, because it's a Function Declaration. It did not run yet.
All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation. We didn't talk about it yet, but that's how the function knows where it was made.
**All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** We didn't talk about it yet, but that's how the function knows where it was made.
Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it.
@ -389,13 +401,13 @@ When on an interview, a frontend developer gets a question about "what's a closu
## Code blocks and loops, IIFE
The examples above concentrated on functions. But Lexical Environments also exist for code blocks `{...}`.
The examples above concentrated on functions. But a Lexical Environment exists for any code block `{...}`.
They are created when a code block runs and contain block-local variables. Here are a couple of examples.
A Lexical Environment is created when a code block runs and contains block-local variables. Here are a couple of examples.
## If
### If
In the example below, when the execution goes into `if` block, the new "if-only" Lexical Environment is created for it:
In the example below, the `user` variable exists only in the `if` block:
<!--
```js run
@ -412,11 +424,13 @@ In the example below, when the execution goes into `if` block, the new "if-only"
![](lexenv-if.png)
The new Lexical Environment gets the enclosing one as the outer reference, so `phrase` can be found. But all variables and Function Expressions declared inside `if` reside in that Lexical Environment and can't be seen from the outside.
When the execution gets into the `if` block, the new "if-only" Lexical Environment is created for it.
It has the reference to the outer one, so `phrase` can be found. But all variables and Function Expressions, declared inside `if`, reside in that Lexical Environment and can't be seen from the outside.
For instance, after `if` finishes, the `alert` below won't see the `user`, hence the error.
## For, while
### For, while
For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for`, then it's also local to that Lexical Environment:
@ -429,9 +443,9 @@ for (let i = 0; i < 10; i++) {
alert(i); // Error, no such variable
```
That's actually an exception, because `let i` is visually outside of `{...}`. But in fact each run of the loop has its own Lexical Environment with the current `i` in it.
Please note: `let i` is visually outside of `{...}`. The `for` construct is somewhat special here: each iteration of the loop has its own Lexical Environment with the current `i` in it.
After the loop, `i` is not visible.
Again, similarly to `if`, after the loop `i` is not visible.
### Code blocks
@ -459,9 +473,13 @@ The code outside of the block (or inside another script) doesn't see variables i
### IIFE
In old scripts, one can find so-called "immediately-invoked function expressions" (abbreviated as IIFE) used for this purpose.
In the past, there were no block-level lexical environment in Javascript.
They look like this:
So programmers had to invent something. And what they did is called "immediately-invoked function expressions" (abbreviated as IIFE).
That's not a thing we should use nowadays, but you can find them in old scripts, so it's better to understand them.
IIFE looks like this:
```js run
(function() {
@ -475,11 +493,11 @@ They look like this:
Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables.
The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so there will be an error:
The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error:
```js run
// Error: Unexpected token (
function() { // <-- JavaScript cannot find function name, meets ( and gives error
// Try to declare and immediately call a function
function() { // <-- Error: Unexpected token (
let message = "Hello";
@ -488,7 +506,7 @@ function() { // <-- JavaScript cannot find function name, meets ( and gives erro
}();
```
We can say "okay, let it be so Function Declaration, let's add a name", but it won't work. JavaScript does not allow Function Declarations to be called immediately:
Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately:
```js run
// syntax error because of parentheses below
@ -497,9 +515,9 @@ function go() {
}(); // <-- can't call Function Declaration immediately
```
So, parenthesis are needed to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression. It needs no name and can be called immediately.
So, parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately.
There are other ways to tell JavaScript that we mean Function Expression:
There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression:
```js run
// Ways to create IIFE
@ -525,68 +543,68 @@ In all the above cases we declare a Function Expression and run it immediately.
## Garbage collection
Lexical Environment objects that we've been talking about are subject to the same memory management rules as regular values.
Usually, a Lexical Environment is cleaned up and deleted after the function run. For instance:
- Usually, Lexical Environment is cleaned up after the function run. For instance:
```js
function f() {
let value1 = 123;
let value2 = 456;
}
```js
function f() {
let value1 = 123;
let value2 = 456;
}
f();
```
f();
```
Here two values are technically the properties of the Lexical Environment. But after `f()` finishes that Lexical Environment becomes unreachable, so it's deleted from the memory.
Here two values are technically the properties of the Lexical Environment. But after `f()` finishes that Lexical Environment becomes unreachable, so it's deleted from the memory.
...But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well:
- ...But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well:
```js
function f() {
let value = 123;
```js
function f() {
let value = 123;
function g() { alert(value); }
function g() { alert(value); }
*!*
return g;
*/!*
}
*!*
return g;
*/!*
}
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
```
let g = f(); // g is reachable, and keeps the outer lexical environment in memory
```
Please note that if `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
- Please note that if `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
```js
function f() {
let value = Math.random();
```js
function f() {
let value = Math.random();
return function() { alert(value); };
}
return function() { alert(value); };
}
// 3 functions in array, every one of them links to Lexical Environment
// from the corresponding f() run
// LE LE LE
let arr = [f(), f(), f()];
```
// 3 functions in array, every one of them links to Lexical Environment
// from the corresponding f() run
// LE LE LE
let arr = [f(), f(), f()];
```
A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there's at least one nested function referencing it.
- A Lexical Environment object dies when it becomes unreachable: when no nested functions remain that reference it. In the code below, after `g` becomes unreachable, the `value` is also cleaned from memory;
In the code below, after `g` becomes unreachable, enclosing Lexical Environment (and hence the `value`) is cleaned from memory;
```js
function f() {
let value = 123;
```js
function f() {
let value = 123;
function g() { alert(value); }
function g() { alert(value); }
return g;
}
return g;
}
let g = f(); // while g is alive
// there corresponding Lexical Environment lives
let g = f(); // while g is alive
// there corresponding Lexical Environment lives
g = null; // ...and now the memory is cleaned up
```
g = null; // ...and now the memory is cleaned up
```
### Real-life optimizations

View file

@ -182,4 +182,4 @@ There are two main differences of `var`:
There's one more minor difference related to the global object, we'll cover that in the next chapter.
These differences are actually a bad thing most of the time. First, we can't create block-local variables. And hoisting just creates more space for errors. So, for new scripts `var` is used exceptionally rarely.
These differences are actually a bad thing most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable.

View file

@ -1,171 +1,155 @@
# Global object
When JavaScript was created, there was an idea of a "global object" that provides all global variables and functions. It was planned that multiple in-browser scripts would use that single global object and share variables through it.
Since then, JavaScript greatly evolved, and that idea of linking code through global variables became much less appealing. In modern JavaScript, the concept of modules took its place.
But the global object still remains in the specification.
The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the host environment.
In a browser it is named "window", for Node.JS it is "global", for other environments it may have another name.
It does two things:
For instance, we can call `alert` as a method of `window`:
1. Provides access to built-in functions and values, defined by the specification and the environment.
For instance, we can call `alert` directly or as a method of `window`:
```js run
alert("Hello");
```js run
alert("Hello");
// the same as
window.alert("Hello");
```
The same applies to other built-ins. E.g. we can use `window.Array` instead of `Array`.
2. Provides access to global Function Declarations and `var` variables. We can read and write them using its properties, for instance:
<!-- no-strict to move variables out of eval -->
```js untrusted run no-strict refresh
var phrase = "Hello";
function sayHi() {
alert(phrase);
}
// can read from window
alert( window.phrase ); // Hello (global var)
alert( window.sayHi ); // function (global function declaration)
// can write to window (creates a new global variable)
window.test = 5;
alert(test); // 5
```
...But the global object does not have variables declared with `let/const`!
```js untrusted run no-strict refresh
*!*let*/!* user = "John";
alert(user); // John
alert(window.user); // undefined, don't have let
alert("user" in window); // false
// the same as
window.alert("Hello");
```
```smart header="The global object is not a global Environment Record"
In versions of ECMAScript prior to ES-2015, there were no `let/const` variables, only `var`. And global object was used as a global Environment Record (wordings were a bit different, but that's the gist).
We can reference other built-in functions like `Array` as `window.Array` and create our own properties on it.
But starting from ES-2015, these entities are split apart. There's a global Lexical Environment with its Environment Record. And there's a global object that provides *some* of the global variables.
## Browser: the "window" object
As a practical difference, global `let/const` variables are definitively properties of the global Environment Record, but they do not exist in the global object.
For historical reasons, in-browser `window` object is a bit messed up.
Naturally, that's because the idea of a global object as a way to access "all global things" comes from ancient times. Nowadays it's not considered to be a good thing. Modern language features like `let/const` do not make friends with it, but old ones are still compatible.
```
1. It provides the "browser window" functionality, besides playing the role of a global object.
## Uses of "window"
In server-side environments like Node.JS, the `global` object is used exceptionally rarely. Probably it would be fair to say "never".
In-browser `window` is sometimes used though.
Usually, it's not a good idea to use it, but here are some examples you can meet.
1. To access exactly the global variable if the function has the local one with the same name.
```js untrusted run no-strict refresh
var user = "Global";
function sayHi() {
var user = "Local";
*!*
alert(window.user); // Global
*/!*
}
sayHi();
```
Such use is a workaround. Would be better to name variables differently, that won't require use to write the code it this way. And please note `"var"` before `user`. The trick doesn't work with `let` variables.
2. To check if a certain global variable or a builtin exists.
For instance, we want to check whether a global function `XMLHttpRequest` exists.
We can't write `if (XMLHttpRequest)`, because if there's no `XMLHttpRequest`, there will be an error (variable not defined).
But we can read it from `window.XMLHttpRequest`:
We can use `window` to access properties and methods, specific to the browser window:
```js run
if (window.XMLHttpRequest) {
alert('XMLHttpRequest exists!')
}
alert(window.innerHeight); // shows the browser window height
window.open('http://google.com'); // opens a new browser window
```
If there is no such global function then `window.XMLHttpRequest` is just a non-existing object property. That's `undefined`, no error, so it works.
2. Top-level `var` variables and function declarations automatically become properties of `window`.
We can also write the test without `window`:
For instance:
```js untrusted run no-strict refresh
var x = 5;
```js
if (typeof XMLHttpRequest == 'function') {
/* is there a function XMLHttpRequest? */
}
alert(window.x); // 5 (var x becomes a property of window)
window.x = 0;
alert(x); // 0, variable modified
```
This doesn't use `window`, but is (theoretically) less reliable, because `typeof` may use a local XMLHttpRequest, and we want the global one.
Please note, that doesn't happen with more modern `let/const` declarations:
```js untrusted run no-strict refresh
let x = 5;
3. To take the variable from the right window. That's probably the most valid use case.
alert(window.x); // undefined ("let" doesn't create a window property)
```
A browser may open multiple windows and tabs. A window may also embed another one in `<iframe>`. Every browser window has its own `window` object and global variables. JavaScript allows windows that come from the same site (same protocol, host, port) to access variables from each other.
3. Also, all scripts share the same global scope, so variables declared in one `<script>` become visible in another ones:
That use is a little bit beyond our scope for now, but it looks like:
```html run
<iframe src="/" id="iframe"></iframe>
<script>
var a = 1;
let b = 2;
</script>
<script>
alert( innerWidth ); // get innerWidth property of the current window (browser only)
alert( Array ); // get Array of the current window (javascript core builtin)
// when the iframe loads...
iframe.onload = function() {
// get width of the iframe window
*!*
alert( iframe.contentWindow.innerWidth );
*/!*
// get the builtin Array from the iframe window
*!*
alert( iframe.contentWindow.Array );
*/!*
};
alert(a); // 1
alert(b); // 2
</script>
```
Here, first two alerts use the current window, and the latter two take variables from `iframe` window. Can be any variables if `iframe` originates from the same protocol/host/port.
4. And, a minor thing, but still: the value of `this` in the global scope is `window`.
## "this" and global object
```js untrusted run no-strict refresh
alert(this); // window
```
Sometimes, the value of `this` is exactly the global object. That's rarely used, but some scripts rely on that.
Why was it made like this? At the time of the language creation, the idea to merge multiple aspects into a single `window` object was to "make things simple". But since then many things changed. Tiny scripts became big applications that require proper architecture.
1. In the browser, the value of `this` in the global area is `window`:
Is it good that different scripts (possibly from different sources) see variables of each other?
No, it's not, because it may lead to naming conflicts: the same variable name can be used in two scripts for different purposes, so they will conflict with each other.
As of now, the multi-purpose `window` is considered a design mistake in the language.
Luckily, there's a "road out of hell", called "Javascript modules".
If we set `type="module"` attribute on a `<script>` tag, then such script is considered a separate "module" with its own top-level scope (lexical environment), not interfering with `window`.
- In a module, `var x` does not become a property of `window`:
```html run
<script type="module">
var x = 5;
alert(window.x); // undefined
</script>
```
- Two modules that do not see variables of each other:
```html run
<script type="module">
let x = 5;
</script>
<script type="module">
alert(window.x); // undefined
alert(x); // Error: undeclared variable
</script>
```
- And, the last minor thing, the top-level value of `this` in a module is `undefined` (why should it be `window` anyway?):
```html run
<script type="module">
alert(this); // undefined
</script>
```
**Using `<script type="module">` fixes the design flaw of the language by separating top-level scope from `window`.**
We'll cover more features of modules later, in the chapter [](info:modules).
## Valid uses of the global object
1. Using global variables is generally discouraged. There should be as few global variables as possible, but if we need to make something globally visible, we may want to put it into `window` (or `global` in Node.js).
Here we put the information about the current user into a global object, to be accessible from all other scripts:
```js run
// outside of functions
alert( this === window ); // true
// explicitly assign it to `window`
window.currentUser = {
name: "John",
age: 30
};
// then, elsewhere, in another script
alert(window.currentUser.name); // John
```
Other, non-browser environments, may use another value for `this` in such cases.
2. We can test the global object for support of modern language features.
2. When a function with `this` is called in non-strict mode, it gets the global object as `this`:
```js run no-strict
// not in strict mode (!)
function f() {
alert(this); // [object Window]
For instance, test if a build-in `Promise` object exists (it doesn't in really old browsers):
```js run
if (!window.Promise) {
alert("Your browser is really old!");
}
f(); // called without an object
```
By specification, `this` in this case must be the global object, even in non-browser environments like Node.JS. That's for compatibility with old scripts, in strict mode `this` would be `undefined`.
3. We can create "polyfills": add functions that are not supported by the environment (say, an old browser), but exist in the modern standard.
```js run
if (!window.Promise) {
window.Promise = ... // custom implementation of the modern language feature
}
```
...And of course, if we're in a browser, using `window` to access browser window features (not as a global object) is completely fine.

View file

@ -0,0 +1,38 @@
```js run demo
function* pseudoRandom(seed) {
let value = seed;
while(true) {
value = value * 16807 % 2147483647
yield value;
}
};
let generator = pseudoRandom(1);
alert(generator.next().value); // 16807
alert(generator.next().value); // 282475249
alert(generator.next().value); // 1622650073
```
Please note, the same can be done with a regular function, like this:
```js run
function pseudoRandom(seed) {
let value = seed;
return function() {
value = value * 16807 % 2147483647;
return value;
}
}
let generator = pseudoRandom(1);
alert(generator()); // 16807
alert(generator()); // 282475249
alert(generator()); // 1622650073
```
That's fine for this context. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere.

View file

@ -23,12 +23,12 @@ If we use `1` as the seed, the values will be:
The task is to create a generator function `pseudoRandom(seed)` that takes `seed` and creates the generator with this formula.
Use example:
Usage example:
```js
let gen = pseudoRandom(1);
let generator = pseudoRandom(1);
alert(gen.next().value); // 16807
alert(gen.next().value); // 282475249
alert(gen.next().value); // 1622650073
alert(generator.next().value); // 16807
alert(generator.next().value); // 282475249
alert(generator.next().value); // 1622650073
```

View file

@ -3,7 +3,7 @@
Regular functions return only one, single value (or nothing).
Generators can return ("yield") multiple values, possibly an infinite number of values, one after another, on-demand.
Generators can return ("yield") multiple values, possibly an infinite number of values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease.
## Generator functions
@ -114,11 +114,11 @@ for(let value of generator) {
}
```
That's a much better-looking way to work with generators than calling `.next().value`, right?
...But please note: the example above shows `1`, then `2`, and that's all. It doesn't show `3`!
That's because a for-of iteration ignores the last `value`, when `done: true`. So the result of `return` is ignored.
So, if we want all results to be shown by `for..of`, we must return them with `yield`:
It's because for-of iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `yield`:
```js run
function* generateSequence() {
@ -152,7 +152,42 @@ alert(sequence); // 0, 1, 2, 3
In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator))
Just like any function, generators may have arguments:
## Converting iterables to generators
Some time ago, in the chapter [](info:iterable) we created an iterable `range` object that returns values `from..to`.
Here, let's remember the code:
```js run
let range = {
from: 1,
to: 5,
// for..of calls this method once in the very beginning
[Symbol.iterator]() {
// ...it returns the iterator object:
// onward, for..of works only with that object, asking it for next values
return {
current: this.from,
last: this.to,
// next() is called on each iteration by the for..of loop
next() {
// it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
alert([...range]); // 1,2,3,4,5
```
Using a generator to make iterable sequences is so much more elegant:
```js run
function* generateSequence(start, end) {
@ -161,31 +196,54 @@ function* generateSequence(start, end) {
}
}
let sequence = [...generateSequence(2,5)];
let sequence = [...generateSequence(1,5)];
alert(sequence); // 2, 3, 4, 5
alert(sequence); // 1, 2, 3, 4, 5
```
Here we had finite generators. but we also can make a generator that yields pseudo-random numbers, infinitely. No one requires a generator to ever stop yielding.
...But what if we'd like to keep a custom `range` object?
We can get the best from both worlds by providing a generator as `Symbol.iterator`:
```js run
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // a shorthand for [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5
```
The `range` object is now iterable. The last variant with a generator is much more concise than the original iterable code, and keeps the same functionality.
```smart header="Generators may continue forever"
In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers.
That surely would require a `break` in `for..of`, otherwise the loop would repeat forever and hang.
```
## Generator composition
A generator may include results from other generators in its yields. That's called "a composition of generators".
Generator composition is a special feature of generators that allows to transparently "embed" generators in each other.
For instance, we'd like to generate a sequence of:
- digits `0..9` (character codes 48..57),
- followed by alphabet letters `a..z` (character codes 65..90)
- followed by uppercased letters `A..Z` (character codes 97..122)
Then we're going to create passwords by selecting characters from it (could add syntax characters as well).
Then we plan to create passwords by selecting characters from it (could add syntax characters as well), but need to generate the sequence first.
We already have `function* generateSequence`, that generates a sequence of numbers, given its `start` and `end`.
We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need.
So let's reuse it.
In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.
Of course, we could call `generateSequence` multiple times and then join the results. That's what we would do with regular functions. But generator composition allows us to do better.
It looks like this:
For generators, we can do better, like this:
```js run
function* generateSequence(start, end) {
@ -216,11 +274,9 @@ for(let code of generatePasswordCodes()) {
alert(str); // 0..9A..Za..z
```
Here the special `yield*` directive is used. It only may apply to another generator and *delegates* the execution to it.
The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, it runs generators and transparently forwards their yields outside, as if they were done by the calling generator itself.
That is, `yield*` makes the execution go inside its argument, e.g `generateSequence(48, 57)`, run it and forward all its yields outside.
In other words, it's like we inserted the code from nested generators, equivalent to this:
The result is the same as if we inlined the code from nested generators:
```js run
function* generateSequence(start, end) {
@ -251,24 +307,24 @@ for(let code of generateAlphaNum()) {
alert(str); // 0..9A..Za..z
```
A generator composition is a way to naturally insert one generator into another. It works even if the flow of values from the nested generator is infinite, without consuming extra memory.
A generator composition is a natural way to insert a flow of one generator into another.
It works even if the flow of values from the nested generator is infinite. It's simple and doesn't use extra memory to store intermediate results.
## "yield" is a two-way road
Till this moment, generators were like "iterators on steroids". But as we'll see now, that is not so. There's a fundamental difference, generators are much more powerful and flexible.
Till this moment, generators were like "iterators on steroids". And that's how they are often used.
That's because `yield` is a two-way road. It not only returns the result outside, but also can pass the value inside the generator.
But in fact they are much more powerful and flexible.
- The call `let result = yield value` inside a generator returns `value` to the outer code, pausing the generator execution.
- The outer code may process that value, and call `next` with an argument: `generator.next(arg)`.
- The generator resumes execution, `result` gets the value of `arg`.
That's because `yield` is a two-way road: it not only returns the result outside, but also can pass the value inside the generator.
So, the argument of `next()` gets into the generator, that may use it to adjust further generation.
To do so, we should call `generator.next(arg)`, with an argument. That argument becomes the result of `yield`.
Let's see an example:
```js run
function* generate() {
function* gen() {
*!*
// Pass a question to the outer code and wait for an answer
let result = yield "2 + 2?"; // (*)
@ -277,31 +333,32 @@ function* generate() {
alert(result);
}
let generator = generate();
let generator = gen();
let question = generator.next().value; // <-- yield returns the value
setTimeout(() => generator.next(4), 2000); // --> in two seconds we pass result
generator.next(4); // --> pass the result into the generator
```
In the illustration below, the rectangle is a generator, and outside it is the "calling code" interacting with it:
![](genYield2.png)
The picture shows in detail what's going on in the line `(*)`:
1. The first call `generator.next()` is always without an argument. It starts the execution and returns the result of the first `yield` ("2+2?"). At this point the generator pauses the execution (still on that line).
2. The result of `yield` gets into the `question` variable in the calling code. The code now can do any tasks, the generator is paused.
3. On thee next call `generator.next(4)`, the generator execution resumes, and `4` gets out of the assignment as the result: `let result = yield ...`.
2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code.
3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`.
In the example above, there're only two `next()`.
Please note, the outer code does not have to immediately call`next(4)`. It may take time to calculate the value. This is also a valid code:
Be sure to wrap your head around it, as the syntax may be a bit odd. It's quite uncommon for a function and the calling code to pass values around to each other. But that's exactly what's going on.
```js
// resume the generator after some time
setTimeout(() => generator.next(4), 1000);
```
To make things even more obvious, here's another example, with more calls:
The syntax may seem a bit odd. It's quite uncommon for a function and the calling code to pass values around to each other. But that's exactly what's going on.
To make things more obvious, here's another example, with more calls:
```js run
function* generate() {
function* gen() {
let ask1 = yield "2 + 2?";
alert(ask1); // 4
@ -311,7 +368,7 @@ function* generate() {
alert(ask2); // 9
}
let generator = generate();
let generator = gen();
alert( generator.next().value ); // "2 + 2?"
@ -343,9 +400,9 @@ To pass an error into a `yield`, we should call `generator.throw(err)`. In that
For instance, here the yield of `"2 + 2?"` leads to an error:
```js run
function* generate() {
function* gen() {
try {
let result = yield "Сколько будет 2 + 2?"; // (1)
let result = yield "2 + 2?"; // (1)
alert("The execution does not reach here, because the exception is thrown above");
} catch(e) {
@ -353,7 +410,7 @@ function* generate() {
}
}
let generator = generate();
let generator = gen();
let question = generator.next().value;
@ -362,7 +419,7 @@ generator.throw(new Error("The answer is not found in my database")); // (2)
*/!*
```
The error, thrown into the generator at the line `(2)` leads to an exception in the line `(1)` with `yield`. That exception is handled as usual. In the example above, `try..catch` catches it and shows.
The error, thrown into the generator at the line `(2)` leads to an exception in the line `(1)` with `yield`. In the example above, `try..catch` catches it and shows.
If we don't catch it, then just like any exception, it "falls out" the generator into the calling code.

View file

@ -118,14 +118,18 @@ That's fine. We immediately have a resolved Promise, nothing wrong with that.
````
```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` for that. They are described below.
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.
```
## Consumers: "then" and "catch"
## Consumers: then, catch, finally
A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using the methods `.then` and `.catch`.
A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using methods `.then`, `.catch` and `.finally`.
The syntax of `.then` is:
### then
The most important, fundamental one is `.then`.
The syntax is:
```js
promise.then(
@ -144,7 +148,7 @@ The second argument of `.then` is a function that:
1. runs when the Promise is rejected, and
2. receives the error.
For instance, here's the reaction to a successfuly resolved promise:
For instance, here's a reaction to a successfuly resolved promise:
```js run
let promise = new Promise(function(resolve, reject) {
@ -190,6 +194,8 @@ promise.then(alert); // shows "done!" after 1 second
*/!*
```
### catch
If we're interested only in errors, then we can use `null` as the first argument: `.then(null, errorHandlingFunction)`. Or we can use `.catch(errorHandlingFunction)`, which is exactly the same:
@ -206,8 +212,55 @@ promise.catch(alert); // shows "Error: Whoops!" after 1 second
The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand.
````smart header="On settled promises `then` runs immediately"
If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately:
### finally
The call `.finally(f)` is similar to `.then(f, f)`, it always runs when the promise is settled: be it resolve or reject.
The idea is that we can perform cleanup in it, e.g. stop our loading indicators in `finally`, as they are not needed any more, like this:
```js
new Promise((resolve, reject) => {
/* do something that takes time, and then call resolve/reject */
})
*!*
// runs when the promise is settled, doesn't matter successfully or not
.finally(() => stop loading indicator)
*/!*
.then(result => show result, err => show error)
```
It's not exactly an alias though. There are several important differences:
1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. We shouldn't need to know it, as our task is usually to perform "general" finalizing procedures.
2. Finally passes through results and errors to the next handler.
For instance, here the result is passed through `finally` to `then`:
```js run
new Promise((resolve, reject) => {
setTimeout(() => resolve("result"), 2000)
})
.finally(() => alert("Promise ready"))
.then(result => alert(result)); // result
```
And here there's an error in the promise, passed through `finally` to `catch`:
```js run
new Promise((resolve, reject) => {
throw new Error("error");
})
.finally(() => alert("Promise ready"))
.catch(err => alert(err)); // error
```
That's very convenient, because finally is not meant to process promise results. So it passes them through.
We'll talk about promise chaining and passing around results in more detail in the next chapter.
3. The last, but not the least, `.finally(f)` is more convenient syntax than `.then(f, f)`: there's no need to duplicate a function.
````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:
```js run
// an immediately resolved promise
@ -219,14 +272,12 @@ promise.then(alert); // done! (shows up right now)
Some tasks may sometimes require time and sometimes finish immediately. The good thing is: the `.then` handler is guaranteed to run in both cases.
````
````smart header="Handlers of `.then`/`.catch` are always asynchronous"
Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch` may still execute first.
````smart header="Handlers of `.then`/`.catch`/`.finally` are always asynchronous"
Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch`/`.finally` may still execute first.
The JavaScript engine has an internal execution queue which gets all `.then/catch` handlers.
The JavaScript engine has an internal execution queue which gets all `.then/catch/finally` handlers.
But it only looks into that queue when the current execution is finished.
In other words, `.then/catch` handlers are pending execution until the engine is done with the current code.
But it only looks into that queue when the current execution is finished. In other words, the handlers are pending execution until the engine is done with the current code.
For instance, here:
@ -240,11 +291,11 @@ promise.then(alert); // this alert shows last (*)
alert("code finished"); // this alert shows first
```
The promise becomes settled immediately, but the engine first finishes the current code, calls `alert`, and only *afterwards* looks into the queue to run `.then` handler.
The promise becomes settled immediately, but the engine first finishes the current code, calls `alert("code finished")`, and only *afterwards* looks into the queue to run `.then` handler.
So the code *after* `.then` ends up always running *before* the Promise's subscribers, even in the case of an immediately-resolved Promise.
Usually that's unimportant, but in some scenarios the order may matter a great deal.
Sometimes that's unimportant, while in some scenarios the order may matter a great deal.
````
Next, let's see more practical examples of how promises can help us to write asynchronous code.
@ -288,7 +339,7 @@ function loadScript(src) {
Usage:
```js run
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
@ -300,11 +351,10 @@ promise.then(script => alert('One more handler to do something else!'));
We can immediately see a few benefits over the callback-based pattern:
```compare minus="Callbacks" plus="Promises"
- We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called.
- There can be only one callback.
+ Promises allow us to do things in the natural order. First, we run `loadScript`, and `.then` we write what to do with the result.
+ We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next section: [Promise Chaining](/promise-chaining).
```
So Promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters.
| Promises | Callbacks |
|----------|-----------|
| Promises allow us to do things in the natural order. First, we run `loadScript(script)`, and `.then` we write what to do with the result. | We must have a `callback` function at our disposal when calling `loadScript(script, callback)`. In other words, we must know what to do with the result *before* `loadScript` is called. |
| We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: [](info:promise-chaining). | There can be only one callback. |
So Promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters.

View file

@ -6,7 +6,8 @@ Are these code fragments equal? In other words, do they behave the same way in a
promise.then(f1).catch(f2);
```
Versus;
Versus:
```js
promise.then(f1, f2);
```

View file

@ -69,7 +69,7 @@ new Promise(function(resolve, reject) {
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
Unlike the chaining, technically we can also add many `.then` to a single promise, like this:
Please note: technically we can also add many `.then` to a single promise. This is not chaining:
```js run
let promise = new Promise(function(resolve, reject) {
@ -92,11 +92,13 @@ promise.then(function(result) {
});
```
...But that's a totally different thing. Here's the picture (compare it with the chaining above):
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it idependantly.
Here's the picture (compare it with the chaining above):
![](promise-then-many.png)
All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`. There is no result-passing between them.
All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`.
In practice we rarely need multiple handlers for one promise. Chaining is used much more often.
@ -165,16 +167,31 @@ loadScript("/article/promise-chaining/one.js")
});
```
This code can be made bit shorter with arrow functions:
```js run
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// scripts are loaded, we can use functions declared there
one();
two();
three();
});
```
Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.
We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom".
Please note that technically it is also possible to write `.then` directly after each promise, without returning them, like this:
Please note that technically we can add `.then` directly to each `loadScript`, like this:
```js run
loadScript("/article/promise-chaining/one.js").then(function(script1) {
loadScript("/article/promise-chaining/two.js").then(function(script2) {
loadScript("/article/promise-chaining/three.js").then(function(script3) {
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// this function has access to variables script1, script2 and script3
one();
two();
@ -184,9 +201,11 @@ loadScript("/article/promise-chaining/one.js").then(function(script1) {
});
```
This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks. Use chaining (return promises from `.then`) to evade it.
This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks.
Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope (here the most nested callback has access to all variables `scriptX`), but that's an exception rather than a rule.
People who start to use promises sometimes don't know about chaining, so they write it this way. Generally, chaining is preferred.
Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables `script1`, `script2`, `script3`. But that's an exception rather than a rule.
````smart header="Thenables"
@ -361,324 +380,10 @@ loadJson('/article/promise-chaining/user.json')
// ...
```
## Error handling
Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
```js run
*!*
fetch('https://no-such-server.blabla') // rejects
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
```
Or, maybe, everything is all right with the server, but the response is not a valid JSON:
```js run
fetch('/') // fetch works fine now, the server responds successfully
*!*
.then(response => response.json()) // rejects: the page is HTML, not a valid json
*/!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
```
In the example below we append `.catch` to handle all errors in the avatar-loading-and-showing chain:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
```
Here `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects, then it would execute.
## Implicit try..catch
The code of the executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
For instance, this code:
```js run
new Promise(function(resolve, reject) {
*!*
throw new Error("Whoops!");
*/!*
}).catch(alert); // Error: Whoops!
```
...Works the same way as this:
```js run
new Promise(function(resolve, reject) {
*!*
reject(new Error("Whoops!"));
*/!*
}).catch(alert); // Error: Whoops!
```
The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example:
```js run
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
throw new Error("Whoops!"); // rejects the promise
*/!*
}).catch(alert); // Error: Whoops!
```
That's so not only for `throw`, but for any errors, including programming errors as well:
```js run
new Promise(function(resolve, reject) {
resolve("ok");
}).then(function(result) {
*!*
blabla(); // no such function
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
```
As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
## Rethrowing
As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
In the example below the `.catch` successfully handles the error:
```js run
// the execution: catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
```
Here the `.catch` block finishes normally. So the next successful handler is called. Or it could return something, that would be the same.
...And here the `.catch` block analyzes the error and throws it again:
```js run
// the execution: catch -> catch -> then
new Promise(function(resolve, reject) {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
*!*
throw error; // throwing this or another error jumps to the next catch
*/!*
}
}).then(function() {
/* never runs here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
```
The handler `(*)` catches the error and just can't handle it, because it's not `URIError`, so it throws it again. Then the execution jumps to the next `.catch` down the chain `(**)`.
In the section below we'll see a practical example of rethrowing.
## Fetch error handling example
Let's improve error handling for the user-loading example.
The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response.
What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and github returns a page with error 404 at `(**)`?
```js run
fetch('no-such-user.json') // (*)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
.then(response => response.json())
.catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
// ...
```
As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist.
That's not good, because the error just falls through the chain, without details: what failed and where.
So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error.
```js run
class HttpError extends Error { // (1)
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) { // (2)
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // HttpError: 404 for .../no-such-user.json
```
1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts the `response` object and saves it in the error. So error-handling code will be able to access it.
2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic.
3. Now `alert` shows better message.
The great thing about having our own class for errors is that we can easily check for it in error-handling code.
For instance, we can make a request, and then if we get 404 -- ask the user to modify the information.
The code below loads a user with the given name from github. If there's no such user, then it asks for the correct name:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`); // (1)
return user;
})
.catch(err => {
*!*
if (err instanceof HttpError && err.response.status == 404) { // (2)
*/!*
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```
Here:
1. If `loadJson` returns a valid user object, then the name is shown `(1)`, and the user is returned, so that we can add more user-related actions to the chain. In that case the `.catch` below is ignored, everything's very simple and fine.
2. Otherwise, in case of an error, we check it in the line `(2)`. Only if it's indeed the HTTP error, and the status is 404 (Not found), we ask the user to reenter. For other errors -- we don't know how to handle, so we just rethrow them.
## Unhandled rejections
What happens when an error is not handled? For instance, after the rethrow as in the example above. Or if we forget to append an error handler to the end of the chain, like here:
```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
}); // no .catch attached
```
Or here:
```js untrusted run refresh
// a chain of promises without .catch at the end
new Promise(function() {
throw new Error("Whoops!");
}).then(function() {
// ...something...
}).then(function() {
// ...something else...
}).then(function() {
// ...but no catch after it!
});
```
In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck".
In practice, that's usually because of the bad code. Indeed, how come there's no error handling?
Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
In the browser we can catch it using the event `unhandledrejection`:
```js run
*!*
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
*/!*
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
```
The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). Now if an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers: the `event` object has the information about the error, so we can do something with it.
Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report about the incident to the server.
In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
## Summary
To summarize, `.then/catch(handler)` returns a new promise that changes depending on what handler does:
If `.then` (or `catch/finally`, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further.
1. If it returns a value or finishes without a `return` (same as `return undefined`), then the new promise becomes resolved, and the closest resolve handler (the first argument of `.then`) is called with that value.
2. If it throws an error, then the new promise becomes rejected, and the closest rejection handler (second argument of `.then` or `.catch`) is called with it.
3. If it returns a promise, then JavaScript waits until it settles and then acts on its outcome the same way.
The picture of how the promise returned by `.then/catch` changes:
Here's a full picture:
![](promise-handler-variants.png)
The smaller picture of how handlers are called:
![](promise-handler-variants-2.png)
In the examples of error handling above the `.catch` was always the last in the chain. In practice though, not every promise chain has a `.catch`. Just like regular code is not always wrapped in `try..catch`.
We should place `.catch` exactly in the places where we want to handle errors and know how to handle them. Using custom error classes can help to analyze errors and rethrow those that we can't handle.
For errors that fall outside of our scope we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments). Such unknown errors are usually unrecoverable, so all we should do is to inform the user and probably report to our server about the incident.

View file

@ -0,0 +1,343 @@
# Error handling with promises
Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections).
Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice.
For instance, in the code below the URL is wrong (no such server) and `.catch` handles the error:
```js run
*!*
fetch('https://no-such-server.blabla') // rejects
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
```
Or, maybe, everything is all right with the server, but the response is not a valid JSON:
```js run
fetch('/') // fetch works fine now, the server responds successfully
*!*
.then(response => response.json()) // rejects: the page is HTML, not a valid json
*/!*
.catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0
```
The easiest way to catch all errors is to append `.catch` to the end of chain:
```js run
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
*!*
.catch(error => alert(error.message));
*/!*
```
Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
## Implicit try..catch
The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an error happens, it gets caught and treated as a rejection.
For instance, this code:
```js run
new Promise((resolve, reject) => {
*!*
throw new Error("Whoops!");
*/!*
}).catch(alert); // Error: Whoops!
```
...Works exactly the same as this:
```js run
new Promise((resolve, reject) => {
*!*
reject(new Error("Whoops!"));
*/!*
}).catch(alert); // Error: Whoops!
```
The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection.
That's so not only in the executor, but in handlers as well. If we `throw` inside `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Here's an example:
```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
throw new Error("Whoops!"); // rejects the promise
*/!*
}).catch(alert); // Error: Whoops!
```
That's so not only for `throw`, but for any errors, including programming errors as well:
```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
blabla(); // no such function
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
```
As a side effect, the final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
## Rethrowing
As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` as we want, and then use a single `.catch` at the end to handle errors in all of them.
In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises.
If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
In the example below the `.catch` successfully handles the error:
```js run
// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
```
Here the `.catch` block finishes normally. So the next successful `.then` handler is called.
In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again:
```js run
// the execution: catch -> catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
*!*
throw error; // throwing this or another error jumps to the next catch
*/!*
}
}).then(function() {
/* never runs here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
```
Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain.
In the section below we'll see a practical example of rethrowing.
## Fetch error handling example
Let's improve error handling for the user-loading example.
The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response.
What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and github returns a page with error 404 at `(**)`?
```js run
fetch('no-such-user.json') // (*)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**)
.then(response => response.json())
.catch(alert); // SyntaxError: Unexpected token < in JSON at position 0
// ...
```
As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist.
That's not good, because the error just falls through the chain, without details: what failed and where.
So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error.
```js run
class HttpError extends Error { // (1)
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) { // (2)
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
loadJson('no-such-user.json') // (3)
.catch(alert); // HttpError: 404 for .../no-such-user.json
```
1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access it.
2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic.
3. Now `alert` shows better message.
The great thing about having our own class for errors is that we can easily check for it in error-handling code.
For instance, we can make a request, and then if we get 404 -- ask the user to modify the information.
The code below loads a user with the given name from github. If there's no such user, then it asks for the correct name:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
*!*
if (err instanceof HttpError && err.response.status == 404) {
*/!*
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err; // (*)
}
});
}
demoGithubUser();
```
Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case.
For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`.
## Unhandled rejections
What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above.
Or we could just forget to append an error handler to the end of the chain, like here:
```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// zero or many promise handlers
}); // without .catch at the end!
```
In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck".
In practice, just like with a regular unhandled errors, it means that something terribly gone wrong, the script probably died.
Most JavaScript engines track such situations and generate a global error in that case. We can see it in the console.
In the browser we can catch such errors using the event `unhandledrejection`:
```js run
*!*
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
*/!*
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
```
The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections).
If an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers, and gets the `event` object with the information about the error, so we can do something.
Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server.
In non-browser environments like Node.JS there are other similar ways to track unhandled errors.
## Summary
- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a handler.
- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones.
- It's normal not to use `.catch` if we don't know how to handle errors (all errors are unrecoverable).
- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them. So that our app never "just dies".
And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete:
```js run
function demoGithubUser() {
let name = prompt("Enter a name?", "iliakan");
*!*
document.body.style.opacity = 0.3; // (1) start the indication
*/!*
return loadJson(`https://api.github.com/users/${name}`)
*!*
.finally(() => { // (2) stop the indication
document.body.style.opacity = '';
return new Promise(resolve => setTimeout(resolve, 0)); // (*)
})
*/!*
.then(user => {
alert(`Full name: ${user.name}.`);
return user;
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("No such user, please reenter.");
return demoGithubUser();
} else {
throw err;
}
});
}
demoGithubUser();
```
Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead.
When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication.
There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain.

View file

@ -0,0 +1,3 @@
function getMessage() {
return "Hello, world!";
}

View file

@ -0,0 +1,41 @@
<script>
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`);
this.name = 'HttpError';
this.response = response;
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new HttpError(response);
}
})
}
</script>
<style>
.promise-avatar-example {
border-radius: 50%;
position: fixed;
left: 10px;
top: 10px;
}
</style>

View file

@ -0,0 +1,3 @@
function three() {
alert(3);
}

View file

@ -0,0 +1,4 @@
{
"name": "iliakan",
"isAdmin": true
}

View file

@ -0,0 +1,3 @@
function one() {
alert(1);
}

View file

@ -0,0 +1,3 @@
function two() {
alert(2);
}

View file

@ -1,4 +0,0 @@
# Async iteration
TODO

View file

@ -0,0 +1,20 @@
# Async iteration and generators
In web-programming, we often need to work with fragmented data that comes piece-by-piece.
That happens when we upload or download a file. Or we need to fetch paginated data.
For
Either we need to download or upload something, or we need
In the previous chapter we saw how `async/await` allows to write asynchronous code.
But they don't solve
Regular iterators work fine with the data that doesn't take time to generate.
A `for..of` loop assumes that we

View file

@ -1,19 +0,0 @@
Please note, the same can be done with a regular function, like this:
```js run
function pseudoRandom(seed) {
let value = seed;
return function() {
value = value * 16807 % 2147483647;
return value;
}
}
let generator = pseudoRandom(1);
alert(generator()); // 16807
alert(generator()); // 282475249
alert(generator()); // 1622650073
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View file

@ -1,9 +0,0 @@
# Async iteration
...
Async generators may initially look complex, but in fact they are not.
The resulting syntax is simple and easy to use.

View file

@ -1,2 +0,0 @@
# Generators

View file

@ -87,14 +87,14 @@ Module scripts, have several important differences, that's why `type="module"` i
```html run
<script type="module">
*!*
alert(button); // HTMLButtonElement: the script can 'see' elements below
alert(typeof button); // object: the script can 'see' the button below
*/!*
// as modules are deferred, the script runs after the whole page is loaded
</script>
<script>
*!*
alert(button); // Error: button is undefined, the script can't see elements below
alert(typeof button); // Error: button is undefined, the script can't see elements below
*/!*
// regular scripts run immediately, before the rest of the page is processed
</script>
@ -102,7 +102,11 @@ Module scripts, have several important differences, that's why `type="module"` i
<button id="button">Button</button>
```
So, when we're using modules, we should be aware that HTML-document can show up before the Javascript application is ready. Certain functionality may not work yet. We should put transparent overlays or "loading indicators", or otherwise ensure that the visitor won't be confused because of it.
Please note: the second script actually works before the first! So we'll see `undefined` first, and then `object`.
That's because modules are deferred, so way wait for the document to be processed. The regular scripts runs immediately, so we saw its output first.
When using modules, we should be aware that HTML-document can show up before the Javascript application is ready. Some functionality may not work yet. We should put transparent overlays or "loading indicators", or otherwise ensure that the visitor won't be confused because of it.
4. Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independantly of other scripts or the HTML document.
@ -301,6 +305,20 @@ alert(admin.name); // *!*Pete*/!*
sayHi(); // Ready to serve, *!*Pete*/!*!
```
### Top-level "this" is undefined
In a module, top-level `this` is undefined (as opposed to a global object in non-module scripts):
```html run height=0
<script>
alert(this); // window
</script>
<script type="module">
alert(this); // undefined
</script>
```
## Summary
To summarize, the core concepts are:

Binary file not shown.