Node.js Generators & Сравнете с обратни повиквания

⚡ Умно обобщение

GeneratorВ Node.js са функции, чието изпълнение може да бъде спряно и възобновено при поискване. Ключовата дума yield поставя функцията на пауза, методът next я възобновява и това сдвояване изравнява дълбоко вложените обратни извиквания в четлив последователен код.

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

Node.js Generators В сравнение с обратните повиквания

В това ръководство ще разгледаме генераторите и как те се различават от обратните извиквания.

Какво представляват генераторите?

Generatorстана популярен в Node.js заради това, което са способни да правят.

  • Generators са изпълнения на функции, които могат да бъдат спрени и възобновени на по-късен етап.
  • Generatorса полезни за концепции като „лениво изпълнение“. Чрез спиране на изпълнението и възобновяване по желание, стойностите се извличат само когато са необходими.

GeneratorИмат двата ключови метода по-долу.

  1. Метод на добива — извиква се вътре във функция, за да спре изпълнението на точния ред, където yield Появява се.
  2. Следващ метод — извиква се от главното приложение за възобновяване на функция, която е дала резултат. Изпълнението продължава до следващото yield, или до края на функцията.

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

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 ms.

Стъпка 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() просто създава обекта генератор и връща резултата веднага; таймерът никога не се стартира и нищо не се отпечатва.

Липсващото парче е runner: малка функция, която извиква 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: Кое да използвате

Generators бяха стандартният начин за писане на плосък асинхронен код преди async/await съществуваше и двете споделят една и съща основна идея за паузиране на функция по средата. Node поддържа async/await първоначално от версия 7.6, което променя препоръката за всеки, който започва нова работа днес.

Аспект Generators асинхронен/изчакване
Търси се бегач Да, написано от вас или от библиотека Не, вградено е в езика
Пауза на ключовата дума yield await
Обработка на грешки Бегачът трябва да препраща грешки Обикновен опит и улов
Може да се направи пауза по средата Да, и може да се възобнови избирателно Не, работи до завършване
Най - доброто за Мързеливи последователности и безкрайни потоци Обикновена асинхронна работа

употреба async/await за асинхронен поток в нов код, тъй като премахва runner-а и възстановява обикновените try и catch. Запазете генераторите за това, което само те правят добре: лениво генериране на стойности, итериране на големи или безкрайни последователности и всеки случай, в който потребителят решава точно кога се изчислява следващата стойност.

Общ Generator Грешки и как да ги поправим

Повечето проблеми с генераторите идват от това, че забравяме, че функцията е инертна, докато нещо не я извика. next(), или от малки синтактични грешки около звездичката. Всеки симптом по-долу посочва причината и решението си.

  • Нищо не се случва, когато генераторът се извика: Извикването му връща итератор, а не резултат. next()или го предайте на бегач.
  • Синтактична грешка при добива: звездичката липсва. yield е валидно само вътре в function*.
  • Стойността от next() пристига неопределена: първи next() извикването не може да достави стойност, защото генераторът не е достигнал yield все още. Изпращайте данни от второто повикване нататък.
  • Безкраен цикъл в бегач: result.done никога не се проверява, така че next() продължава да се извиква и след края. Винаги връщай, когато е готово, е true.
  • Грешките изчезват тихо: Отхвърлянето в асинхронната стъпка никога не се връща обратно в генератора. Препраща се с it.throw(err) така че try and catch, написан вътре в генератора, действително да може да го види и обработи.

Въпроси и Отговори

Обект с две свойства: value, държейки каквото е било предадено, и done, булева стойност, която става true, след като генераторът завърши. Извършен е тест на бегачите, за да се знае кога да се спре.

Да. Генератор, съдържащ безкраен цикъл, е безопасен, защото нищо не се изчислява, докато не се извика next(). Това прави генераторите подходящи за потоци от идентификатори или страницирани резултати.

Да. AI инструментите заменят yield с await и премахват runner-а изцяло. RevПреглеждайте внимателно ленивите последователности, защото генератор, който никога не завършва, няма еквивалент на директно асинхронно чакане.

AI асистенти tracдали бегачът е спрял да извиква следващия път, дали обратно извикване за възобновяване никога не е било извикано или дали никога не е достигнато „done“. Тези три фактора са причина за почти всеки блокирал генератор.

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

Обобщете тази публикация с: