Node.js Generators и сравнить с обратными вызовами
⚡ Умное резюме
GeneratorВ Node.js функции `yield` — это функции, выполнение которых можно приостанавливать и возобновлять по требованию. Ключевое слово `yield` приостанавливает выполнение функции, метод `next` возобновляет его, и такое сочетание упрощает глубоко вложенные колбэки, преобразуя их в читаемый последовательный код.
В этом пошаговом руководстве мы рассмотрим генераторы и их отличия от функций обратного вызова.
Что такое генераторы?
Generatorстали популярными в Node.js из-за того, на что они способны.
- Generators — это выполнение функций, которые можно приостановить и возобновить позже.
- GeneratorФункции полезны для таких концепций, как «ленивое выполнение». Приостанавливая и возобновляя выполнение по желанию, значения извлекаются только тогда, когда они необходимы.
GeneratorСуществуют два основных метода, описанных ниже.
- Метод доходности — вызывается внутри функции для остановки выполнения именно в той строке, где
yieldпоявляется. - Следующий метод — вызывается из основного приложения для возобновления функции, которая ранее завершилась с ошибкой. Выполнение продолжается до следующего вызова.
yieldили до конца выполнения функции.
Рассмотрим пример использования генераторов. В примере определена простая функция Add, которая складывает два числа, останавливая выполнение в разных точках, чтобы показать, как работают генераторы.
function* Add(x) { yield x + 1; // The value passed into the second next() call lands here var y = yield(null); return x + y; } var gen = Add(5); gen.next(); // runs to the first yield, returns { value: 6, done: false } gen.next(6); // resumes, y becomes 6, returns { value: 11, done: true }
Code объяснение
- Первый шаг — определить функцию-генератор. Обратите внимание на следующее:
*добавлен вfunctionключевое слово. Функция вызывается.Addи принимает параметрx. -
yieldЭто ключевое слово специфично для генераторов и приостанавливает выполнение функции. Выполнение останавливается здесь до тех пор, пока...next()вызывается. - Второй
yieldи приостанавливает выполнение функции, и получает значение: то, что передается следующему элементу.next()вызов становится значениемy. - призвание
gen.next()Функция запускается в первый раз до первого запуска.yield. Вызовgen.next(6)возобновляет его, назначает 6y, и возвращаетx + y.
⚠️ Исправление: В первоначальном объявлении содержалась следующая строка: y = 6 сразу после var y = yield(null);Это присваивание перезаписывает любое другое значение. next() поставки, что противоречит цели примера и делает второй вариант невозможным. yield бессмысленно. Значение теперь передается через gen.next(6)Таким образом генератор фактически получает данные от вызывающей стороны.
Обратные вызовы против генераторов
Generatorчтобы решить проблему, известную как «ад коллбэков». Функции коллбэков иногда становятся настолько глубоко вложенными в процессе разработки приложения Node.js, что за ними трудно следить.
Вот тут-то и пригодятся генераторы. Один из самых распространенных примеров — создание функций таймера. В приведенном ниже примере создается простая функция задержки по времени, а затем она вызывается с задержками в 1000, 2000 и 3000 мс.
Шаг 1) Определите функцию обратного вызова с необходимым кодом задержки по времени.
function Timedelay(ptime, callback) { setTimeout(function() { callback("Pausing for " + ptime); }, ptime); // the delay must use ptime, the parameter name }
Code объяснение
- Функция под названием
Timedelayсоздается с параметромptime, которая содержит количество миллисекунд, на которое нужно сделать паузу. setTimeoutожидает указанное количество миллисекунд, а затем вызывает предоставленную функцию обратного вызова.
⚠️ Исправление: исходный код прошёл проверку time в качестве второго аргумента к setTimeoutно переменная с именем отсутствует time существует — параметр равен ptimeВ том виде, в котором написано, это приводит к ошибке. ReferenceError: time is not defined.
Шаг 2) Теперь рассмотрим код, использующий коллбэки. Для создания цепочки задержек в 1000, 2000 и 3000 миллисекунд коллбэки вложены друг в друга.
Timedelay(1000, function(message) { console.log(message); Timedelay(2000, function(message) { console.log(message); Timedelay(3000, function(message) { console.log(message); }); }); });
Code объяснение
TimedelayВызывается как функция обратного вызова со значением 1000.- Внутри этого обратного вызова,
TimedelayЕго снова вызывают с числом 2000. - Внутри того,
TimedelayЭто называется в третий раз с числом 3000.
⚠️ Исправление: каждый обратный вызов в исходном логе msg, но параметр называется messageВсе три линии бросили ReferenceError: msg is not defined.
Из приведенного выше кода видно, что с каждым дополнительным вызовом он становится все более запутанным — именно такое вложенное расположение вызовов и описывает «ад коллбэков».
Шаг 3) Теперь рассмотрим аналогичное поведение, реализованное с помощью генераторов. Последовательность читается как три плоские линии, а не как три уровня вложенности.
function* Messages() { console.log(yield Timedelay(1000)); console.log(yield Timedelay(2000)); console.log(yield Timedelay(3000)); }
Code объяснение
- Для вызова функции-генератора определена функция-генератор.
Timedelayфункции. yieldиспользуется сTimedelayи 1000 в качестве значения параметра.yieldиспользуется снова с числом 2000, и еще раз с числом 3000.
⚠️ Исправление: оригинал написал console,log(...) Во всех трех строках вместо точки стоит запятая, что является синтаксической ошибкой и препятствует полному анализу файла.
Как запустить Generator с функцией бегуна
Приведённый выше генератор звучит великолепно, но сам по себе ничего не делает. Именно эту деталь опускают большинство объяснений, и именно поэтому простая версия может выглядеть как волшебство. Генератор — это приостановленный механизм, и что-то должно постоянно нажимать на кнопку. Вызов Messages() Просто создает объект-генератор и немедленно возвращает управление; таймер не запускается и ничего не выводится на экран.
Недостающим звеном является исполняемый файл: небольшая функция, которая вызывает... next()ожидает завершения асинхронной работы, а затем вызывает next() И снова результат.
// Timedelay now hands its result back to the runner function Timedelay(ptime) { return function(resume) { setTimeout(function() { resume("Pausing for " + ptime); }, ptime); }; } // The runner drives the generator to completion function run(generatorFunction) { var it = generatorFunction(); function step(value) { var result = it.next(value); if (result.done) return; // result.value is the function returned by Timedelay result.value(step); } step(); } run(Messages);
Три фактора обеспечивают это. Во-первых, Timedelay Теперь функция обратного вызова больше не принимается напрямую; она возвращает функцию, которую может вызвать исполнитель, возвращая управление, когда срабатывает таймер. Во-вторых, it.next(value) передает предыдущий результат в приостановленный генератор, что и позволяет yield появиться с правильной стороны задания. Во-третьих, бегун останавливается, как только result.done становится истинным, поэтому рекурсия завершается естественным образом. Этот шаблон запуска — именно то, что предоставляли такие библиотеки, как co, и это механизм, который async/await Впоследствии он был включен в сам язык.
Generators против async/await: что использовать?
GeneratorРаньше стандартным способом написания плоского асинхронного кода были асинхронные методы. async/await существовали, и у них обоих есть общая идея — приостановка выполнения функции на полпути. Node поддерживает async/await Это изменение вступает в силу начиная с версии 7.6, что меняет рекомендации для всех, кто начинает новую работу сегодня.
| Аспект | Generators | асинхронный / ожидание |
|---|---|---|
| Требуется курьер | Да, написано вами или библиотекой. | Нет, это заложено в языке. |
| Ключевое слово "Пауза" | yield |
await |
| Обработка ошибок | Бегун должен сообщать об ошибках. | Обычная попытка поймать |
| Можно сделать паузу на полпути | Да, и его можно возобновить выборочно. | Нет, выполняется до конца |
| лучше всего для | Ленивые последовательности и бесконечные потоки | Обычная асинхронная работа |
Используйте async/await Для асинхронного потока в новом коде, поскольку это устраняет исполнитель и восстанавливает обычные конструкции try и catch. Генераторы следует использовать только для того, что они умеют делать хорошо: ленивое создание значений, итерация больших или бесконечных последовательностей и любой случай, когда потребитель точно решает, когда будет вычислено следующее значение.
Общие Generator Ошибки и способы их исправления
Большинство проблем с генераторами возникают из-за того, что забывают, что функция инертна до тех пор, пока что-либо не вызовет её. next()или из-за небольших синтаксических ошибок вокруг звездочки. Для каждого симптома ниже указана его причина и способ устранения.
- При вызове генератора ничего не происходит: Вызов этой функции возвращает итератор, а не результат.
next()или передать его бегуну. - SyntaxError on yield: Звездочка отсутствует.
yieldдействительно только внутриfunction*. - Значение, полученное из функции next(), является неопределенным: первый
next()Вызов не может передать значение, поскольку генератор еще не достиг точкиyieldТем не менее. Начиная со второго вызова, отправляйте данные. - Бесконечная петля у бегуна:
result.doneникогда не проверяется, поэтомуnext()Вызов продолжается и после завершения. Утверждение "Всегда возвращаться, когда сделано" истинно. - Ошибки исчезают незаметно: Отклонение внутри асинхронного шага никогда не передается обратно в генератор. Передайте его дальше вместе с
it.throw(err)чтобы конструкция try-catch, написанная внутри генератора, могла это увидеть и обработать.





