Node.js Generators & Порівняти зі зворотними викликами
⚡ Розумний підсумок
GeneratorУ Node.js це функції, виконання яких можна призупинити та відновити за запитом. Ключове слово 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)відновлює його, призначає 6 доy, і повертається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.
З наведеного вище коду видно, що з кожним додатковим викликом він стає все більш безладним — саме це вкладення описує callback hell.
Крок 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(...) з комою замість крапки в усіх трьох рядках, що є синтаксичною помилкою та взагалі запобігає розбору файлу.
Як запустити 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, написаний всередині генератора, міг його побачити та обробити.





