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

@ -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,9 @@
function* pseudoRandom(seed) {
let value = seed;
while(true) {
value = value * 16807 % 2147483647
yield value;
}
};

View file

@ -0,0 +1,21 @@
describe("pseudoRandom", function() {
it("follows the formula", function() {
let generator = pseudoRandom(1);
assert.equal(generator.next().value, 16807);
assert.equal(generator.next().value, 282475249);
assert.equal(generator.next().value, 1622650073);
});
it("returns same value for the same seed", function() {
let generator1 = pseudoRandom(123);
let generator2 = pseudoRandom(123);
assert.deepEqual(generator1.next(), generator2.next());
assert.deepEqual(generator1.next(), generator2.next());
assert.deepEqual(generator1.next(), generator2.next());
});
});

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

@ -0,0 +1,34 @@
# Pseudo-random generator
There are many areas where we need random data.
One of them is testing. We may need random data: text, numbers etc, to test things out well.
In Javascript, we could use `Math.random()`. But if something goes wrong, we'd like to be able to repeat the test, using exactly the same data.
For that, so called "seeded pseudo-random generators" are used. They take a "seed", the first value, and then generate next ones using a formula. So that the same seed yields the same sequence, and hence the whole flow is easily reproducable. We only need to remember the seed to repeat it.
An example of such formula, that generates somewhat uniformly distributed values:
```
next = previous * 16807 % 2147483647
```
If we use `1` as the seed, the values will be:
1. `16807`
2. `282475249`
3. `1622650073`
4. ...and so on...
The task is to create a generator function `pseudoRandom(seed)` that takes `seed` and creates the generator with this formula.
Usage example:
```js
let generator = pseudoRandom(1);
alert(generator.next().value); // 16807
alert(generator.next().value); // 282475249
alert(generator.next().value); // 1622650073
```

View file

@ -0,0 +1,458 @@
# Generators
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. They work great with [iterables](info:iterable), allowing to create data streams with ease.
## Generator functions
To create a generator, we need a special syntax construct: `function*`, so-called "generator function".
It looks like this:
```js
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
```
When `generateSequence()` is called, it does not execute the code. Instead, it returns a special object, called "generator".
```js
// "generator function" creates "generator object"
let generator = generateSequence();
```
The `generator` object can be perceived as a "frozen function call":
![](generateSequence-1.png)
Upon creation, the code execution is paused at the very beginning.
The main method of a generator is `next()`. When called, it resumes execution till the nearest `yield <value>` statement. Then the execution pauses, and the value is returned to the outer code.
For instance, here we create the generator and get its first yielded value:
```js run
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
*!*
let one = generator.next();
*/!*
alert(JSON.stringify(one)); // {value: 1, done: false}
```
The result of `next()` is always an object:
- `value`: the yielded value.
- `done`: `false` if the code is not finished yet, otherwise `true`.
As of now, we got the first value only:
![](generateSequence-2.png)
Let's call `generator.next()` again. It resumes the execution and returns the next `yield`:
```js
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
```
![](generateSequence-3.png)
And, if we call it the third time, then the execution reaches `return` statement that finishes the function:
```js
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
```
![](generateSequence-4.png)
Now the generator is done. We should see it from `done:true` and process `value:3` as the final result.
New calls `generator.next()` don't make sense any more. If we make them, they return the same object: `{done: true}`.
There's no way to "roll back" a generator. But we can create another one by calling `generateSequence()`.
So far, the most important thing to understand is that generator functions, unlike regular function, do not run the code. They serve as "generator factories". Running `function*` returns a generator, and then we ask it for values.
```smart header="`function* f(…)` or `function *f(…)`?"
That's a minor religious question, both syntaxes are correct.
But usually the first syntax is preferred, as the star `*` denotes that it's a generator function, it describes the kind, not the name, so it should stick with the `function` keyword.
```
## Generators are iterable
As you probably already guessed looking at the `next()` method, generators are [iterable](info:iterable).
We can get loop over values by `for..of`:
```js run
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, then 2
}
```
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`!
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() {
yield 1;
yield 2;
*!*
yield 3;
*/!*
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, then 2, then 3
}
```
Naturally, as generators are iterable, we can all related functionality, e.g. the spread operator `...`:
```js run
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
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))
## 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) {
for (let i = start; i <= end; i++) {
yield i;
}
}
let sequence = [...generateSequence(1,5)];
alert(sequence); // 1, 2, 3, 4, 5
```
...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
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 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(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need.
In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end.
For generators, we can do better, like this:
```js run
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
*!*
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
*/!*
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
```
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.
The result is the same as if we inlined the code from nested generators:
```js run
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generateAlphaNum() {
*!*
// yield* generateSequence(48, 57);
for (let i = 48; i <= 57; i++) yield i;
// yield* generateSequence(65, 90);
for (let i = 65; i <= 90; i++) yield i;
// yield* generateSequence(97, 122);
for (let i = 97; i <= 122; i++) yield i;
*/!*
}
let str = '';
for(let code of generateAlphaNum()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
```
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". And that's how they are often used.
But in fact they are much more powerful and flexible.
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.
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* gen() {
*!*
// Pass a question to the outer code and wait for an answer
let result = yield "2 + 2?"; // (*)
*/!*
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield returns the value
generator.next(4); // --> pass the result into the generator
```
![](genYield2.png)
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. 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`.
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:
```js
// resume the generator after some time
setTimeout(() => generator.next(4), 1000);
```
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* gen() {
let ask1 = yield "2 + 2?";
alert(ask1); // 4
let ask2 = yield "3 * 3?"
alert(ask2); // 9
}
let generator = gen();
alert( generator.next().value ); // "2 + 2?"
alert( generator.next(4).value ); // "3 * 3?"
alert( generator.next(9).done ); // true
```
The execution picture:
![](genYield2-2.png)
1. The first `.next()` starts the execution... It reaches the first `yield`.
2. The result is returned to the outer code.
3. The second `.next(4)` passes `4` back to the generator as the result of the first `yield`, and resumes the execution.
4. ...It reaches the second `yield`, that becomes the result of the generator call.
5. The third `next(9)` passes `9` into the generator as the result of the second `yield` and resumes the execution that reaches the end of the function, so `done: true`.
It's like a "ping-pong" game. Each `next(value)` (excluding the first one) passes a value into the generator, that becomes the result of the current `yield`, and then gets back the result of the next `yield`.
## generator.throw
As we observed in the examples above, the outer code may pass a value into the generator, as the result of `yield`.
...But it can also initiate (throw) an error there. That's natural, as an error is a kind of result.
To pass an error into a `yield`, we should call `generator.throw(err)`. In that case, the `err` is thrown in the line with that `yield`.
For instance, here the yield of `"2 + 2?"` leads to an error:
```js run
function* gen() {
try {
let result = yield "2 + 2?"; // (1)
alert("The execution does not reach here, because the exception is thrown above");
} catch(e) {
alert(e); // shows the error
}
}
let generator = gen();
let question = generator.next().value;
*!*
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`. 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.
The current line of the calling code is the line with `generator.throw`, labelled as `(2)`. So we can catch it here, like this:
```js run
function* generate() {
let result = yield "2 + 2?"; // Error in this line
}
let generator = generate();
let question = generator.next().value;
*!*
try {
generator.throw(new Error("The answer is not found in my database"));
} catch(e) {
alert(e); // shows the error
}
*/!*
```
If we don't catch the error there, then, as usual, it falls through to the outer calling code (if any) and, if uncaught, kills the script.
## Summary
- Generators are created by generator functions `function*(…) {…}`.
- Inside generators (only) there exists a `yield` operator.
- The outer code and the generator may exchange results via `next/yield` calls.
In modern Javascript, generators are rarely used. But sometimes they come in handy, because the ability of a function to exchange data with the calling code during the execution is quite unique.
Also, in the next chapter we'll learn async generators, which are used to read streams of asynchronously generated data in `for` loop.
In web-programming we often work with streamed data, e.g. need to fetch paginated results, so that's a very important use case.