Sobes.tech
Middle+

Защо обикновените генератори позволяват писането на асинхронен код?

sobes.tech AI

Отговор от AI

Обикновените генератори в JavaScript позволяват писането на асинхронен код благодарение на способността им да спират и възобновяват изпълнението, както и да предават стойности в двете посоки (yield и next()).

Ето как работи това в контекста на асинхронността:

  1. Спиране на изпълнението (yield): Когато достигне до оператора yield, генераторът се спира и връща стойност. В асинхронния код тази стойност може да бъде promise или друго "действие", което трябва да бъде изпълнено асинхронно.

  2. Изчакване на асинхронен резултат: Външният код (например "runner" или обвивка за генератора) получава стойността, предадена чрез yield. Ако това е promise, той чака неговото разрешаване или отхвърляне.

  3. Възобновяване на изпълнението (next()): След като асинхронната операция (разрешаването на promise) приключи, асинхронният резултат (стойността на promise) се предава обратно в генератора чрез метода next(). Генераторът възобновява изпълнението си от мястото, където е бил спрян.

  4. Предаване на резултата: Стойността, предадена в next(), става резултатът от израза yield вътре в генератора. Така генераторът може "да получи" резултата от асинхронната операция и да продължи работата с него, както ако кодът беше синхронен.

Този модел създава илюзия за синхронно изпълнение за асинхронните операции, правейки кода по-лесен за четене и управление в сравнение с традиционното използване на callback-и или сложни вериги .then().

Пример за такова използване е обвивката тип co (макар че сега по-широко разпространени са async/await, които по същество са синтактичен захар над генераторите и promises):

// Пример за използване на генератор за асинхронност
function* myAsyncGenerator() {
  try {
    const result1 = yield Promise.resolve(1); // Спиране, връщане на promise
    console.log('Получихме първия резултат:', result1); // Възобновяване с предаване на резултата

    const result2 = yield new Promise(resolve => setTimeout(() => resolve(2), 500)); // Още една асинхронна операция
    console.log('Получихме втория резултат:', result2);

    return 'Готово'; // Завършване на генератора
  } catch (error) {
    console.error('Възникна грешка:', error);
  }
}

// Прост "runner" за генератора (опростено)
function run(generatorFunc) {
  const generator = generatorFunc();

  function handleNext(newValue) {
    const result = generator.next(newValue);

    if (!result.done) {
      if (result.value instanceof Promise) {
        result.value.then(handleNext).catch(handleError);
      } else {
        handleNext(result.value);
      }
    } else {
      console.log('Генераторът е завършил с резултат:', result.value);
    }
  }

  function handleError(error) {
      generator.throw(error);
  }

  handleNext(); // Стартиране на генератора
}

// Стартираме нашия асинхронен генератор
run(myAsyncGenerator);

В този пример yield Promise.resolve(1) спира генератора. "Runner" (или co) чака, докато promise се разреши, и след това възобновява генератора, предавайки стойността 1 чрез next(1). Тази стойност 1 става резултатът от yield Promise.resolve(1) вътре в генератора, позволявайки да бъде обработена на следващия ред (console.log). Този механизъм позволява имитация на синхронен поток на изпълнение за асинхронни операции.