Node.js Generators & Порівняти зі зворотними викликами

⚡ Розумний підсумок

GeneratorУ Node.js це функції, виконання яких можна призупинити та відновити за запитом. Ключове слово yield призупиняє функцію, метод next відновлює її, і це сполучення згладжує глибоко вкладені зворотні виклики до читабельного послідовного коду.

  • ⏸️ Основна поведінка: Генератор призупиняється на кожному yield та відновлює роботу точно з того місця, де зупинився, коли викликається next.
  • ✳️ Декларація: Зірочка після ключового слова 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.

З наведеного вище коду видно, що з кожним додатковим викликом він стає все більш безладним — саме це вкладення описує callback hell.

Крок 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(...) з комою замість крапки в усіх трьох рядках, що є синтаксичною помилкою та взагалі запобігає розбору файлу.

Як запустити a 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(), або передайте його бігуну.
  • Синтаксична помилка на yield: зірочка відсутня. yield дійсний лише всередині function*.
  • Значення з next() надходить невизначеним: перший next() виклик не може передати значення, оскільки генератор не досяг yield поки що. Надсилайте дані з другого дзвінка далі.
  • Нескінченний цикл у бігуні: result.done ніколи не перевіряється, тому next() викликається після завершення. Завжди повертайте, коли виконано, це true.
  • Помилки зникають непомітно: Відмова всередині асинхронного кроку ніколи не передається назад до генератора. Пересилайте її за допомогою it.throw(err) щоб try and catch, написаний всередині генератора, міг його побачити та обробити.

Поширені запитання

Об'єкт з двома властивостями: value, утримуючи все, що було здано, та done, логічне значення, яке стає істинним після завершення роботи генератора. Виконується перевірка бігунів, щоб знати, коли зупинитися.

Так. Генератор, що містить нескінченний цикл, є безпечним, оскільки нічого не обчислюється, доки не буде викликано next(). Це робить генератори добре підходящими для потоків ідентифікаторів або сторінкових результатів.

Так. AI інструменти замінюють yield на await та повністю видаляють бігун. RevУважно переглядайте ліниві послідовності, оскільки генератор, який ніколи не завершується, не має прямого еквівалента асинхронного очікування.

Асистенти ШІ tracчи виконувач зупинив виклик наступним, чи зворотний виклик resume ніколи не був викликаний, чи ніколи не було досягнуто done. Ці три фактори пояснюють майже кожен зупинений генератор.

Ні. Оголошено асинхронний генератор. async function* і споживається разом з for await...ofВін поєднує обидві ідеї та підходить для потокової передачі асинхронних даних, таких як курсори бази даних.

Підсумуйте цей пост за допомогою: