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