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



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow