es6
This commit is contained in:
parent
6160d38f35
commit
be6f03f10e
1 changed files with 23 additions and 18 deletions
|
@ -19,9 +19,7 @@ function* generateSequence() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
При запуске `generateSequence()` код такой функции не выполняется!
|
При запуске `generateSequence()` код такой функции не выполняется. Вместо этого она возвращает специальный объект, который как раз и называют "генератором".
|
||||||
|
|
||||||
Вместо этого она возвращает специальный объект, который как раз и называют "генератором".
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// generator function создаёт generator
|
// generator function создаёт generator
|
||||||
|
@ -48,7 +46,9 @@ function* generateSequence() {
|
||||||
|
|
||||||
let generator = generateSequence();
|
let generator = generateSequence();
|
||||||
|
|
||||||
|
*!*
|
||||||
let one = generator.next();
|
let one = generator.next();
|
||||||
|
*/!*
|
||||||
|
|
||||||
alert(JSON.stringify(one)); // {value: 1, done: false}
|
alert(JSON.stringify(one)); // {value: 1, done: false}
|
||||||
```
|
```
|
||||||
|
@ -76,12 +76,14 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
|
||||||
<img src="generateSequence-4.png">
|
<img src="generateSequence-4.png">
|
||||||
|
|
||||||
|
|
||||||
Функция завершена. Внешний код увидит это из свойства `done:true` и обработает `value:3`, как окончательный результат. Новые вызовы `generator.next()` больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: `{done: true}`.
|
Функция завершена. Внешний код должен увидеть это из свойства `done:true` и обработать `value:3`, как окончательный результат.
|
||||||
|
|
||||||
|
Новые вызовы `generator.next()` больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: `{done: true}`.
|
||||||
|
|
||||||
"Открутить назад" завершившийся генератор нельзя, но можно создать новый ещё одним вызовом `generateSequence()` и выполнить его.
|
"Открутить назад" завершившийся генератор нельзя, но можно создать новый ещё одним вызовом `generateSequence()` и выполнить его.
|
||||||
|
|
||||||
[smart header="`function* (…)` или `function *(…)`?"]
|
[smart header="`function* (…)` или `function *(…)`?"]
|
||||||
Технически можно ставить звёздочку как сразу после `function`, так и позже, перед названием. В интернет можно найти обе эти формы записи, они верны:
|
Можно ставить звёздочку как сразу после `function`, так и позже, перед названием. В интернет можно найти обе эти формы записи, они верны:
|
||||||
```js
|
```js
|
||||||
function* f() {
|
function* f() {
|
||||||
// звёздочка после function
|
// звёздочка после function
|
||||||
|
@ -92,7 +94,9 @@ function *f() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Автор этого текста полагает, что правильнее использовать первый вариант `function*`, так как звёздочка относится к типу объявляемой сущности (`function*` -- "функция-генератор"), а не к её названию. Конечно, это всего лишь рекомендация-мнение, не обязательное к выполнению, работать будет и так и эдак.
|
Технически, нет разницы, но писать то так то эдак -- довольно странно, надо остановиться на чём-то одном.
|
||||||
|
|
||||||
|
Автор этого текста полагает, что правильнее использовать первый вариант `function*`, так как звёздочка относится к типу объявляемой сущности (`function*` -- "функция-генератор"), а не к её названию. Конечно, это всего лишь рекомендация-мнение, не обязательное к выполнению, работать будет в любом случае.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,7 +104,7 @@ function *f() {
|
||||||
|
|
||||||
## Генератор -- итератор
|
## Генератор -- итератор
|
||||||
|
|
||||||
Как вы, наверно, уже догадались по наличию метода `next()`, генератор является итерируемым объектом.
|
Как вы, наверно, уже догадались по наличию метода `next()`, генератор связан с [итераторами](/iterator). В частности, он является итерируемым объектом.
|
||||||
|
|
||||||
Его можно перебирать и через `for..of`:
|
Его можно перебирать и через `for..of`:
|
||||||
|
|
||||||
|
@ -261,7 +265,7 @@ for(let code of generateAlphaNum()) {
|
||||||
alert(str); // 0..9A..Za..z
|
alert(str); // 0..9A..Za..z
|
||||||
```
|
```
|
||||||
|
|
||||||
Код выше по поведению полностью идентичен варианту с `yield*`. При этом, конечно, переменные вложенного генератора не попадают во внешний, "делегирование" только выводит результаты во внешний поток.
|
Код выше по поведению полностью идентичен варианту с `yield*`. При этом, конечно, переменные вложенного генератора не попадают во внешний, "делегирование" только выводит результаты `yield` во внешний поток.
|
||||||
|
|
||||||
Композиция -- это естественное встраивание одного генератора в поток другого. При композиции значения из вложенного генератора выдаются "по мере готовности". Поэтому она будет работать даже если поток данных из вложенного генератора оказался бесконечным или ожидает какого-либо условия для завершения.
|
Композиция -- это естественное встраивание одного генератора в поток другого. При композиции значения из вложенного генератора выдаются "по мере готовности". Поэтому она будет работать даже если поток данных из вложенного генератора оказался бесконечным или ожидает какого-либо условия для завершения.
|
||||||
|
|
||||||
|
@ -317,7 +321,7 @@ setTimeout(() => generator.next(4), 2000);
|
||||||
|
|
||||||
В примере выше -- только два `next`.
|
В примере выше -- только два `next`.
|
||||||
|
|
||||||
Возможно, происходящее будет проще понять, если их больше:
|
Увеличим их количество, чтобы стал более понятен общий поток выполнения:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -446,6 +450,7 @@ try {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// генератор для получения и показа аватара
|
// генератор для получения и показа аватара
|
||||||
|
// он yield'ит промисы
|
||||||
function* showUserAvatar() {
|
function* showUserAvatar() {
|
||||||
|
|
||||||
let userFetch = yield fetch('/article/generator/user.json');
|
let userFetch = yield fetch('/article/generator/user.json');
|
||||||
|
@ -466,7 +471,8 @@ function* showUserAvatar() {
|
||||||
return img.src;
|
return img.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
// вспомогательная функция-чернорабочий для выполнения промисов
|
// вспомогательная функция-чернорабочий
|
||||||
|
// для выполнения промисов из generator
|
||||||
function execute(generator, yieldValue) {
|
function execute(generator, yieldValue) {
|
||||||
|
|
||||||
let next = generator.next(yieldValue);
|
let next = generator.next(yieldValue);
|
||||||
|
@ -477,7 +483,7 @@ function execute(generator, yieldValue) {
|
||||||
err => generator.throw(err)
|
err => generator.throw(err)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// return из генератора (обработаем результат)
|
// обработаем результат return из генератора
|
||||||
// обычно здесь вызов callback или что-то в этом духе
|
// обычно здесь вызов callback или что-то в этом духе
|
||||||
alert(next.value);
|
alert(next.value);
|
||||||
}
|
}
|
||||||
|
@ -617,11 +623,11 @@ co(function*() {
|
||||||
```
|
```
|
||||||
|
|
||||||
[smart header="Устаревший `yield function(callback)`"]
|
[smart header="Устаревший `yield function(callback)`"]
|
||||||
Отдельно заметим вариант с `yield function(callback)`. Такие функции (с единственным-аргументом callback'ом) называют "thunk".
|
Отдельно заметим вариант с `yield function(callback)`. Такие функции, с единственным-аргументом callback'ом, в англоязычной литературе называют "thunk".
|
||||||
|
|
||||||
Функция обязана выполниться и вызвать (асинхронно) либо `callback(err)` с ошибкой, либо `callback(null, result)` с результатом.
|
Функция обязана выполниться и вызвать (асинхронно) либо `callback(err)` с ошибкой, либо `callback(null, result)` с результатом.
|
||||||
|
|
||||||
Этот способ использования является устаревшим, так как там, где можно использовать `yield function(callback)`, можно использовать и промисы. При этом промисы мощнее. Но в старом коде его ещё можно встретить.
|
Использование таких функций в `yield` является устаревшим подходом, так как там, где можно использовать `yield function(callback)`, можно использовать и промисы. При этом промисы мощнее. Но в старом коде его ещё можно встретить.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,7 +647,7 @@ function* gen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function* gen2() {
|
function* gen2() {
|
||||||
let result = yield new Promise(
|
let result = yield new Promise( // (1)
|
||||||
resolve => setTimeout(resolve, 1000, 'hello')
|
resolve => setTimeout(resolve, 1000, 'hello')
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
|
@ -650,7 +656,7 @@ function* gen2() {
|
||||||
|
|
||||||
Это -- отличный вариант для библиотеки `co`. Композиция `yield* gen()` вызывает `gen()` в потоке внешнего генератора. Аналогично делает и `yield* gen()`.
|
Это -- отличный вариант для библиотеки `co`. Композиция `yield* gen()` вызывает `gen()` в потоке внешнего генератора. Аналогично делает и `yield* gen()`.
|
||||||
|
|
||||||
Поэтому `yield new Promise` из `gen2()` попадает напрямую в библиотеку `co`, как еслы бы он был сделан так:
|
Поэтому `yield new Promise` из строки `(1)` в `gen2()` попадает напрямую в библиотеку `co`, как если бы он был сделан так:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -750,14 +756,13 @@ let user = yield fetchUser(url);
|
||||||
// let user = yield* fetchUser(url);
|
// let user = yield* fetchUser(url);
|
||||||
```
|
```
|
||||||
|
|
||||||
То есть, можно сделать `yield` генератора, `co()` его выполнит и передаст значение обратно. Как мы видели выше, библиотека `co` -- довольно всеядна, поэтому будет работать и так и эдак.
|
То есть, можно сделать `yield` генератора, `co()` его выполнит и передаст значение обратно. Как мы видели выше, библиотека `co` -- довольно всеядна. Однако, рекомендуется использовать для вызова функций-генераторов именно `yield*`.
|
||||||
Однако, рекомендуется использовать для вызова функций-генераторов именно `yield*`.
|
|
||||||
|
|
||||||
Причин для этого несколько:
|
Причин для этого несколько:
|
||||||
<ol>
|
<ol>
|
||||||
<li>Делегирование генераторов `yield*` -- это встроенный механизм JavaScript. Вместо возвращения значения обратно в `co`, выполнения кода библиотеки... Мы просто используем возможности языка. Это правильнее.</li>
|
<li>Делегирование генераторов `yield*` -- это встроенный механизм JavaScript. Вместо возвращения значения обратно в `co`, выполнения кода библиотеки... Мы просто используем возможности языка. Это правильнее.</li>
|
||||||
<li>Поскольку не происходит лишних вызовов, это быстрее по производительности.</li>
|
<li>Поскольку не происходит лишних вызовов, это быстрее по производительности.</li>
|
||||||
<li>И, наконец, пожалуй, самое приятное -- делегирование генераторов не портит стек.</li>
|
<li>И, наконец, пожалуй, самое приятное -- делегирование генераторов сохраняет стек.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Проиллюстрируем последнее на примере:
|
Проиллюстрируем последнее на примере:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue