Szukaj…
Wprowadzenie
Funkcje generatora (zdefiniowane przez słowo kluczowe function*
) działają jako coroutines, generując serię wartości zgodnie z żądaniem iteratora.
Składnia
- funkcja * nazwa (parametry) {wartość wydajności; zwracana wartość}
- generator = nazwa (argumenty)
- {wartość, zrobiona} = generator.next (wartość)
- {wartość, zrobiona} = generator.return (wartość)
- generator.throw (błąd)
Uwagi
Funkcje generatora są funkcją wprowadzoną w ramach specyfikacji ES 2015 i nie są dostępne we wszystkich przeglądarkach. Są również w pełni obsługiwane w Node.js od v6.0
. Szczegółowa lista kompatybilności przeglądarki znajduje się w dokumentacji MDN , a dla Węzła na stronie node.green .
Funkcje generatora
Funkcja generatora jest tworzona z deklaracją function*
. Po wywołaniu jego ciało nie jest natychmiast wykonywane. Zamiast tego zwraca obiekt generatora , którego można użyć do „przejścia” przez wykonanie funkcji.
Wyrażenie yield
wewnątrz ciała funkcji określa punkt, w którym wykonanie może zostać zawieszone i wznowione.
function* nums() {
console.log('starting'); // A
yield 1; // B
console.log('yielded 1'); // C
yield 2; // D
console.log('yielded 2'); // E
yield 3; // F
console.log('yielded 3'); // G
}
var generator = nums(); // Returns the iterator. No code in nums is executed
generator.next(); // Executes lines A,B returning { value: 1, done: false }
// console: "starting"
generator.next(); // Executes lines C,D returning { value: 2, done: false }
// console: "yielded 1"
generator.next(); // Executes lines E,F returning { value: 3, done: false }
// console: "yielded 2"
generator.next(); // Executes line G returning { value: undefined, done: true }
// console: "yielded 3"
Wyjście z wczesnej iteracji
generator = nums();
generator.next(); // Executes lines A,B returning { value: 1, done: false }
generator.next(); // Executes lines C,D returning { value: 2, done: false }
generator.return(3); // no code is executed returns { value: 3, done: true }
// any further calls will return done = true
generator.next(); // no code executed returns { value: undefined, done: true }
Zgłoszenie błędu do funkcji generatora
function* nums() {
try {
yield 1; // A
yield 2; // B
yield 3; // C
} catch (e) {
console.log(e.message); // D
}
}
var generator = nums();
generator.next(); // Executes line A returning { value: 1, done: false }
generator.next(); // Executes line B returning { value: 2, done: false }
generator.throw(new Error("Error!!")); // Executes line D returning { value: undefined, done: true}
// console: "Error!!"
generator.next(); // no code executed. returns { value: undefined, done: true }
Iteracja
Generator jest iterowalny . Może być zapętlony za pomocą instrukcji for...of
i używany w innych konstrukcjach zależnych od protokołu iteracji.
function* range(n) {
for (let i = 0; i < n; ++i) {
yield i;
}
}
// looping
for (let n of range(10)) {
// n takes on the values 0, 1, ... 9
}
// spread operator
let nums = [...range(3)]; // [0, 1, 2]
let max = Math.max(...range(100)); // 99
Oto kolejny przykład użycia generatora niestandardowych obiektów iterowalnych w ES6. Tutaj użyto anonimowej funkcji generatora function *
.
let user = {
name: "sam", totalReplies: 17, isBlocked: false
};
user[Symbol.iterator] = function *(){
let properties = Object.keys(this);
let count = 0;
let isDone = false;
for(let p of properties){
yield this[p];
}
};
for(let p of user){
console.log( p );
}
Przesyłanie wartości do generatora
Możliwe jest przesłanie wartości do generatora poprzez przekazanie jej do metody next()
.
function* summer() {
let sum = 0, value;
while (true) {
// receive sent value
value = yield;
if (value === null) break;
// aggregate values
sum += value;
}
return sum;
}
let generator = summer();
// proceed until the first "yield" expression, ignoring the "value" argument
generator.next();
// from this point on, the generator aggregates values until we send "null"
generator.next(1);
generator.next(10);
generator.next(100);
// close the generator and collect the result
let sum = generator.next(null).value; // 111
Delegowanie do innego generatora
Z funkcji generatora sterowanie może zostać przekazane do innej funkcji generatora przy użyciu yield*
.
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var it = g2();
console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); // 3
console.log(it.next()); // 4
console.log(it.next()); // 5
console.log(it.next()); // undefined
Interfejs Iterator-Observer
Generator to połączenie dwóch rzeczy - Iterator
i Observer
.
Iterator
Iterator jest czymś, gdy wywołany zwraca iterable
. iterable
jest czymś, co można iterować. Począwszy od ES6 / ES2015, wszystkie kolekcje (Array, Map, Set, WeakMap, WeakSet) są zgodne z umową Iterable.
Generator (iterator) to producent. W iteracji
PULL
konsumenta jest wartością od producenta.
Przykład:
function *gen() { yield 5; yield 6; }
let a = gen();
Kiedykolwiek zadzwonić a.next()
, jesteś w istocie pull
-ing wartość z iteracyjnej i pause
wykonanie na yield
. Przy następnym wywołaniu a.next()
wykonanie zostanie wznowione od poprzednio wstrzymanego stanu.
Obserwator
Generator jest również obserwatorem, za pomocą którego można przesyłać niektóre wartości z powrotem do generatora.
function *gen() {
document.write('<br>observer:', yield 1);
}
var a = gen();
var i = a.next();
while(!i.done) {
document.write('<br>iterator:', i.value);
i = a.next(100);
}
Tutaj możesz zobaczyć, że yield 1
jest używana jak wyrażenie, które daje pewną wartość. Wartość, którą ocenia, jest wartością wysłaną jako argument do a.next
funkcji a.next
.
Tak więc, po raz pierwszy i.value
Będzie pierwszą uzyskaną wartością ( 1
), a kontynuując iterację do następnego stanu, wysyłamy wartość z powrotem do generatora za pomocą a.next(100)
.
Przeprowadzanie asynchronizacji z generatorami
Generatory są szeroko stosowane z funkcją spawn
(z taskJS lub co), gdzie funkcja pobiera generator i pozwala nam pisać kod asynchroniczny w sposób synchroniczny. Nie oznacza to, że kod asynchroniczny jest konwertowany na kod synchronizacji / wykonywany synchronicznie. Oznacza to, że możemy pisać kod, który wygląda jak sync
ale wewnętrznie nadal jest async
.
Synchronizacja jest ZABLOKOWANA; Async czeka. Pisanie kodu, który blokuje jest łatwe. Podczas PULLowania wartość pojawia się w pozycji przypisania. Gdy PUSHing, wartość pojawia się w pozycji argumentu wywołania zwrotnego.
Korzystając z iteratorów, PULL
wartość od producenta. Gdy używasz wywołań zwrotnych, PUSH
producenta PUSH
wartość do pozycji argumentu wywołania zwrotnego.
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Tutaj a.next()
wartość z a.next()
a po drugie, v => {...}
jest wywołaniem zwrotnym, a wartość jest PUSH
edytowana do pozycji argumentu v
funkcji wywołania zwrotnego.
Za pomocą tego mechanizmu pull-push możemy pisać programowanie asynchroniczne w ten sposób,
let delay = t => new Promise(r => setTimeout(r, t));
spawn(function*() {
// wait for 100 ms and send 1
let x = yield delay(100).then(() => 1);
console.log(x); // 1
// wait for 100 ms and send 2
let y = yield delay(100).then(() => 2);
console.log(y); // 2
});
Patrząc na powyższy kod, piszemy kod asynchroniczny, który wygląda, jakby się blocking
(instrukcje return czekają 100 ms, a następnie kontynuują wykonywanie), ale tak naprawdę waiting
. pause
i resume
właściwości generatora pozwala nam wykonać tę niesamowitą sztuczkę.
Jak to działa ?
Funkcja odradzania używa yield promise
aby PODCIĄGNĄĆ stan obietnicy od generatora, czeka, aż obietnica zostanie rozpatrzona, i PUSH przesyła rozstrzygniętą wartość z powrotem do generatora, aby mógł ją wykorzystać.
Użyj tego teraz
Dzięki generatorom i funkcji odradzania możesz wyczyścić cały kod asynchroniczny w NodeJS, aby wyglądał na zsynchronizowany. Ułatwi to debugowanie. Również kod będzie wyglądał schludnie.
Ta funkcja będzie dostępna w przyszłych wersjach JavaScript - async...await
. Ale możesz ich używać dzisiaj w ES2015 / ES6 za pomocą funkcji spawn zdefiniowanej w bibliotekach - taskjs, co lub bluebird
Przepływ asynchroniczny z generatorami
Generatory to funkcje, które mogą wstrzymywać, a następnie wznawiać wykonywanie. Pozwala to emulować funkcje asynchroniczne przy użyciu bibliotek zewnętrznych, głównie q lub co. Zasadniczo pozwala pisać funkcje, które czekają na wyniki asynchroniczne, aby kontynuować:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
Umożliwia to pisanie kodu asynchronicznego tak, jakby był synchroniczny. Ponadto spróbuj złapać pracę nad kilkoma blokami asynchronicznymi. Jeśli obietnica zostanie odrzucona, błąd zostanie przechwycony przez następny haczyk:
function asyncError() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error('Something went wrong'))
}, 100)
})
}
q.spawn(function * () {
try {
var result = yield asyncError()
} catch (e) {
console.error(e) // Something went wrong
}
})
Korzystanie z co działałoby dokładnie tak samo, ale z co(function * (){...})
zamiast q.spawn