Pytanie Generatory Javascript: zrozumienie ich


Jestem prawie pewien, że moje zrozumienie generatorów jest z natury uszkodzone. Wszystkie zasoby online wydają się być w konflikcie i powoduje to niezwykle trudne i dezorientujące doświadczenie edukacyjne.

Z tego co rozumiem, yield słowo kluczowe umożliwia aktualnie wykonywalny blok kodu poczekaj na wartość zamiast wyrzucać pozostały kod do wykonania wewnątrz wywołania zwrotnego. Tak więc, jak wskazano w większości tutoriali, możesz użyć tego:

(function *() {
     // Wait until users have be got and put into value of `results`
     var results = yield db.get("users");
     // And continue
     view.display(results);
})();

Zamiast:

db.get("user", function(results) {
    view.display(results);
});

Racja, wszystko dobrze i dobrze, dopóki nie spróbuję napisać własnych generatorów. Wpadłem na kilka problemów:

  • Pierwszy przykładowy kod I powyżej nie będzie działać, ponieważ nie ma nic do iteracji nad generatorem, prawda? Jakiś wyższy byt musi zadzwonić .next gdzieś, prawda?
  • Całe API będzie musiało zostać przepisane bezpośrednio na wywołania we / wy, aby obsługiwać generatory, prawda?
  • Z tego co zebrałem, yield zdaje się oznaczać poczekaj na wartość najbardziej ogólne przypadki użycia, podczas gdy w części implementacyjnej (czytaj: zwracana wartość do / wewnątrz db.get) yield zdaje się oznaczać wyślij tę wartość do aktualnie oczekującego bloku, aby wznowić wykonywanie.

Weź na przykład:

function *fn() {
    yield 1;
    yield "a";
}

var gen = fn();
gen.next(); // 1
gen.next(); // "a";

yield w tym kontekście wysyła wartości z powrotem zamiast czekać na wyniki. W pierwszym przykładzie powyżej oczekuje na wyniki z db.get oraz wznowienie wykonywania zamiast "zwracania" lub zwracania wartości. Jeśli db.get przypadek jest prawdziwy, czy to nie jest z natury zsynchronizowane? Mam na myśli, czy to nie jest dokładnie to samo co:

(function() {
     //Wait for the results
    var results = fs.readFileSync("users.txt");
    // Use results
    view.display(results);
})();

Niestety, jeśli z jakiegoś pytania wynika jednoznacznie (chyba jedyne, co jasne), to nie rozumiem generatorów. Mam nadzieję, że mogę uzyskać tutaj pewien wgląd.


18
2017-12-25 03:59


pochodzenie


Nawet jeśli ta odpowiedź dotyczy Pythonadyskusja na temat generatorów i wydajności jest nadal aktualna i może pomóc w zrozumieniu. - Vincent Ramdhanie
Również ten przewodnik może pomóc (szczególnie fibonacci przykład IMO) - Passerby
@VincentRamdhanie Doskonałe wyjaśnienie generatora! Uczy mnie innego użycia generatora! - Herrington Darkholme


Odpowiedzi:


TL; DR: istotą generatora jest kontrolowanie zawieszenia wykonywania kodu.

Do samego generatora można się odwoływać to.

Podsumowując, należy wyróżnić trzy elementy: 1. funkcja generatora 2. generator 3. wygenerowany wynik

Funkcja generatora jest po prostu function z gwiazdą w głowie i (opcjonalnie) yield w jego ciele.

function *generator() {
  console.log('Start!');
  var i = 0;
  while (true) {
    if (i < 3)
      yield i++;
  }
}

var gen = generator();
// nothing happens here!!

Funkcja generatora sama w sobie nic nie robi, tylko zwraca generator, w powyższym przypadku, gen. Żadne wyjście konsoli tutaj, ponieważ tylko po zwróconym generator„s next metoda nazywana jest ciałem funkcja generatora będzie działać. Generator ma kilka metod, z których najważniejsza jest next. next uruchamia kod i zwraca wartość Wynik generatora.

var ret = gen.next();
// Start!
console.log(ret);
// {value: 0, done: false}

ret powyżej jest Wynik generatora. Ma dwie właściwości: value, wartość, którą dajesz funkcja generatora, i done, flaga wskazująca, czy funkcja generatora powrót.

console.log(gen.next());
// {value: 1, done: false}
console.log(gen.next());
// {value: 2, done: false}
console.log(gen.next());
// {value: undefined, done: true}

W tym momencie nikt nie oczekuje od ciebie zrozumienia generatora, a przynajmniej nie asynchronicznej mocy generatora.

Mówiąc prościej, generator ma dwie funkcje:

  • można wyskoczyć z funkcji i pozwolić zewnętrznemu kodowi określić, kiedy wskoczyć z powrotem do funkcji.
  • Kontrola wywołania asynchronicznego może odbywać się poza twoim kodem

W kodzie, yield wyskakuje poza funkcję, i next(val) przeskakuje z powrotem do funkcji i przekazuje wartość z powrotem do funkcji. Kod zewnętrzny może obsłużyć wywołanie asynchroniczne i określić odpowiedni czas na przełączenie na własny kod.

Ponownie sprawdź próbkę:

var gen = generator();
console.log('generated generator');
console.log(gen.next().value);
// mock long long processing
setTimeout(function() {
  console.log(gen.next().value);
  console.log('Execute after timer fire');
}, 1000);
console.log('Execute after timer set');

/* result:
    generated generator
    start
    0
    Execute after timer set
    1
    Execute after timer fire
*/

Widzieć? Funkcja generatora nie obsługuje wywołania zwrotnego. Zewnętrzny kod to robi.

Baza jest tutaj. Możesz rozwinąć ten kod, aby obsługiwać pełną asynchronię, zachowując jednocześnie funkcję generatora, taką jak synchronizacja.

Załóżmy na przykład geturl jest asynchronicznym wywołaniem, które zwraca promise obiekt. Możesz pisać var html = yield getUrl('www.stackoverflow.com'); To skacze poza twój kod. I zewnętrzny kod zrobi takie rzeczy jak:

var ret = gen.next();
ret.then(function (fetchedHTML) {
  // jumps back to your generator function
  // and assign fetchHTML to html in your code
  gen.next(fetchedHTML);
});

Aby uzyskać bardziej kompletny przewodnik, zobacz to. I repozytorium jak współ, galaktyka, zawieszać itd.


20
2017-12-25 08:42





żaden z elementów asynchronicznych, jeśli jest częścią generatorów. Generatory po prostu wstrzymują i wznawiają bloki kodu. cała asynchroniczna magia ma miejsce, gdy używasz czegoś, co nazywam "silnikiem generatora" https://github.com/visionmedia/co.

w zasadzie, co gen.next() to jest return the last yielded wartość i pozwala zwrócić wartość, jeśli yield jest przypisany do czegoś, np. var something = yield 1. więc jeśli masz blok kodu:

function* genfun() {
  var a = yield 1
  var b = yield 2
}

var gen = genfun()

gen.next() // returns the first yielded value via `{value: 1}`
gen.next(1) // sets `a` as 1, returns the next yielded value via `{value: 2}`
gen.next(2) // sets `b` as 2, the generator is done, so it returns `{done: true}`

gen.throw(err) jest taki sam jak następny, z wyjątkiem błędu generowanego zamiast przypisanego do zmiennej.

tak działają silniki sterujące przepływem - otrzymujesz kolejną wartość, która jest prawdopodobnie oddzwanianiem lub czymś podobnym. wykonaj wywołanie zwrotne i nie rób tego gen.next() dopóki połączenie zwrotne nie zostanie zakończone.


3
2017-12-25 06:34





Te dwa przykłady nie są takie same. Kiedy wydajisz, funkcja teraz skutecznie staje się oddzwanianiem, czekającym na wykonanie po zakończeniu db.get ("users"). W ten sposób funkcja nie blokuje wykonywania innych funkcji. Pomyśl o tym, aby włączyć funkcje synchroniczne w funkcje asynchroniczne, informując system, że możesz zatrzymać się w określonych punktach.


1
2017-12-25 06:17