Node.js Generators и сравнить с обратными вызовами

⚡ Умное резюме

GeneratorВ Node.js функции `yield` — это функции, выполнение которых можно приостанавливать и возобновлять по требованию. Ключевое слово `yield` приостанавливает выполнение функции, метод `next` возобновляет его, и такое сочетание упрощает глубоко вложенные колбэки, преобразуя их в читаемый последовательный код.

  • ⏸️ Основное поведение: Генератор приостанавливается при каждом вызове функции yield и возобновляет работу точно с того места, где остановился, при вызове следующей функции yield.
  • ✳️ Декларация: Звездочка после ключевого слова function указывает на то, что функция является генератором, а не обычной функцией.
  • 🔄 Двусторонний канал связи: Значение, переданное в функцию next, становится результатом выражения yield, которое приостановило выполнение функции.
  • 🪆 Ад обратных вызовов: Вложенные функции обратного вызова становятся нечитаемыми по мере добавления шагов, и именно для решения этой проблемы были созданы генераторы.
  • 🏃 Требуется курьер: Генератор не управляет собой; для выполнения асинхронной работы функция-исполнитель должна многократно вызывать функцию next.
  • 🦥 Ленивое исполнение: Приостановка и возобновление работы программы позволяют ей получать значения только в тот момент, когда они необходимы.

Node.js GeneratorПо сравнению с обратными вызовами

В этом пошаговом руководстве мы рассмотрим генераторы и их отличия от функций обратного вызова.

Что такое генераторы?

Generatorстали популярными в Node.js из-за того, на что они способны.

  • Generators — это выполнение функций, которые можно приостановить и возобновить позже.
  • GeneratorФункции полезны для таких концепций, как «ленивое выполнение». Приостанавливая и возобновляя выполнение по желанию, значения извлекаются только тогда, когда они необходимы.

GeneratorСуществуют два основных метода, описанных ниже.

  1. Метод доходности — вызывается внутри функции для остановки выполнения именно в той строке, где yield появляется.
  2. Следующий метод — вызывается из основного приложения для возобновления функции, которая ранее завершилась с ошибкой. Выполнение продолжается до следующего вызова. yieldили до конца выполнения функции.

Рассмотрим пример использования генераторов. В примере определена простая функция Add, которая складывает два числа, останавливая выполнение в разных точках, чтобы показать, как работают генераторы.

Node.js Generators

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 объяснение

  1. Первый шаг — определить функцию-генератор. Обратите внимание на следующее: * добавлен в function ключевое слово. Функция вызывается. Add и принимает параметр x.
  2. yield Это ключевое слово специфично для генераторов и приостанавливает выполнение функции. Выполнение останавливается здесь до тех пор, пока... next() вызывается.
  3. Второй yield и приостанавливает выполнение функции, и получает значение: то, что передается следующему элементу. next() вызов становится значением y.
  4. призвание gen.next() Функция запускается в первый раз до первого запуска. yield. Вызов gen.next(6) возобновляет его, назначает 6 y, и возвращает x + y.

⚠️ Исправление: В первоначальном объявлении содержалась следующая строка: y = 6 сразу после var y = yield(null);Это присваивание перезаписывает любое другое значение. next() поставки, что противоречит цели примера и делает второй вариант невозможным. yield бессмысленно. Значение теперь передается через gen.next(6)Таким образом генератор фактически получает данные от вызывающей стороны.

Обратные вызовы против генераторов

Generatorчтобы решить проблему, известную как «ад коллбэков». Функции коллбэков иногда становятся настолько глубоко вложенными в процессе разработки приложения Node.js, что за ними трудно следить.

Вот тут-то и пригодятся генераторы. Один из самых распространенных примеров — создание функций таймера. В приведенном ниже примере создается простая функция задержки по времени, а затем она вызывается с задержками в 1000, 2000 и 3000 мс.

Шаг 1) Определите функцию обратного вызова с необходимым кодом задержки по времени.

Обратные вызовы против. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

    }, ptime);   // the delay must use ptime, the parameter name
}

Code объяснение

  1. Функция под названием Timedelay создается с параметром ptime, которая содержит количество миллисекунд, на которое нужно сделать паузу.
  2. setTimeout ожидает указанное количество миллисекунд, а затем вызывает предоставленную функцию обратного вызова.

⚠️ Исправление: исходный код прошёл проверку time в качестве второго аргумента к setTimeoutно переменная с именем отсутствует time существует — параметр равен ptimeВ том виде, в котором написано, это приводит к ошибке. ReferenceError: time is not defined.

Шаг 2) Теперь рассмотрим код, использующий коллбэки. Для создания цепочки задержек в 1000, 2000 и 3000 миллисекунд коллбэки вложены друг в друга.

Обратные вызовы против. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

            console.log(message);
        });
    });
});

Code объяснение

  1. Timedelay Вызывается как функция обратного вызова со значением 1000.
  2. Внутри этого обратного вызова, Timedelay Его снова вызывают с числом 2000.
  3. Внутри того, Timedelay Это называется в третий раз с числом 3000.

⚠️ Исправление: каждый обратный вызов в исходном логе msg, но параметр называется messageВсе три линии бросили ReferenceError: msg is not defined.

Из приведенного выше кода видно, что с каждым дополнительным вызовом он становится все более запутанным — именно такое вложенное расположение вызовов и описывает «ад коллбэков».

Шаг 3) Теперь рассмотрим аналогичное поведение, реализованное с помощью генераторов. Последовательность читается как три плоские линии, а не как три уровня вложенности.

Обратные вызовы против. Generators

function* Messages() {
    console.log(yield Timedelay(1000));
    console.log(yield Timedelay(2000));
    console.log(yield Timedelay(3000));
}

Code объяснение

  1. Для вызова функции-генератора определена функция-генератор. Timedelay функции.
  2. yield используется с Timedelay и 1000 в качестве значения параметра.
  3. 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, написанная внутри генератора, могла это увидеть и обработать.

Часто задаваемые вопросы (FAQ)

Объект, обладающий двумя свойствами: valueудерживая все, что было получено, и doneЭто логическая переменная, которая становится истинной после завершения работы генератора. Проверка, выполняемая программой-исполнителем, позволяет определить момент остановки.

Да. Генератор, содержащий бесконечный цикл, безопасен, поскольку вычисления не выполняются до вызова функции next(). Это делает генераторы хорошо подходящими для потоков идентификаторов или результатов с постраничной разбивкой.

Да. AI Инструменты заменяют оператор yield на оператор wait и полностью удаляют исполнитель. RevВнимательно изучите ленивые последовательности, поскольку у генератора, который никогда не завершается, нет прямого эквивалента async await.

Помощники AI tracНапример, прекратил ли бегун вызов следующего задания, не был ли вызван обратный вызов для возобновления работы или же задание так и не было выполнено. Эти три фактора объясняют почти все случаи зависания генератора.

Нет. Объявлен асинхронный генератор. async function* и поглощен for await...ofОн сочетает в себе обе идеи и подходит для потоковой передачи асинхронных данных, таких как курсоры баз данных.

Подведем итог этой публикации следующим образом: