Dlaczego zwykłe generatory pozwalają pisać kod asynchroniczny?
sobes.tech AI
Odpowiedź od AI
Typowe generatory w JavaScript pozwalają pisać kod asynchroniczny dzięki ich zdolności do wstrzymywania i wznawiania wykonywania, a także do przekazywania wartości w obu kierunkach (za pomocą yield i next()).
Oto jak to działa w kontekście asynchroniczności:
-
Wstrzymanie wykonania (
yield): Gdy osiągnięty zostanie operatoryield, generator jest wstrzymywany i zwraca wartość. W kodzie asynchronicznym, ta wartość może być obietnicą (promise) lub innym "działaniem", które musi zostać wykonane asynchronicznie. -
Oczekiwanie na wynik asynchroniczny: Zewnętrzny kod (np. "runner" lub opakowanie dla generatora) otrzymuje wartość przekazaną przez
yield. Jeśli jest to promise, czeka na jego rozwiązanie lub odrzucenie. -
Wznowienie wykonania (
next()): Po zakończeniu operacji asynchronicznej (rozwiązaniu promise), wynik asynchroniczny (wartość promise) jest przekazywany z powrotem do generatora przez metodęnext(). Generator wznawia wykonanie od miejsca, w którym został wstrzymany. -
Przekazanie wyniku: Wartość przekazana do
next()staje się wynikiem wyrażeniayieldwewnątrz generatora. W ten sposób generator może "otrzymać" wynik operacji asynchronicznej i kontynuować z nim pracę, tak jakby kod był synchroniczny.
Ten wzorzec tworzy iluzję synchronicznego wykonania dla operacji asynchronicznych, czyniąc kod bardziej czytelnym i zarządzalnym w porównaniu z tradycyjnym użyciem callbacków lub skomplikowanych łańcuchów .then().
Przykładem tego są opakowania typu co (chociaż obecnie bardziej popularne są async/await, które są w zasadzie cukrem składniowym nad generatorami i promise'ami):
// Przykład użycia generatora do asynchroniczności
function* myAsyncGenerator() {
try {
const result1 = yield Promise.resolve(1); // Wstrzymanie, zwraca promise
console.log('Pierwszy wynik:', result1); // Wznawianie z wartością
const result2 = yield new Promise(resolve => setTimeout(() => resolve(2), 500)); // Kolejna operacja asynchroniczna
console.log('Drugi wynik:', result2);
return 'Gotowe'; // Zakończenie generatora
} catch (error) {
console.error('Wystąpił błąd:', error);
}
}
// "Runner" prosty dla generatora (uproszczony)
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('Generator zakończył z wynikiem:', result.value);
}
}
function handleError(error) {
generator.throw(error);
}
handleNext(); // Uruchom generator
}
// Uruchamiamy nasz asynchroniczny generator
run(myAsyncGenerator);
W tym przykładzie yield Promise.resolve(1) wstrzymuje generator. "Runner" (lub co) czeka, aż promise się rozwiąże, a następnie wznawia generator, przekazując wartość 1 przez next(1). Ta wartość 1 staje się wynikiem yield Promise.resolve(1) wewnątrz generatora, co pozwala na jej obsługę w następnej linii (console.log). Ten mechanizm pozwala na symulację synchronicznego przepływu wykonania dla operacji asynchronicznych.