Różnica między async / await i wydajnością ES6 z generatorami


82

Właśnie czytałem ten fantastyczny artykuł « Generatory » i wyraźnie podkreśla tę funkcję, która jest funkcją pomocniczą do obsługi funkcji generatora:

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

co, jak zakładam, jest mniej więcej sposobem asynczaimplementowania słowa kluczowego z async/ await. Więc pytanie brzmi, jeśli tak jest, to jaka jest różnica między awaitsłowem kluczowym a yieldsłowem kluczowym? Czy awaitzawsze zmienia coś w obietnicę, a yieldnie daje takiej gwarancji? To moje najlepsze przypuszczenie!

Możesz również zobaczyć, jak async/ awaitjest podobne do yieldgeneratorów w tym artykule, w którym opisuje on funkcję „spawn”, funkcje asynchroniczne ES7 .


1
funkcja async -> a coroutine. generator -> iterator, który używa programu do zarządzania wewnętrznym mechanizmem iteracji. await zawiesza coroutine, podczas gdy yield zwraca wynik z coroutine, którego używa jakiś generator
— David Haim

1
async/awaitnie jest częścią ES7. Przeczytaj opis tagu.
— Felix Kling

@david haim, tak, ale async await jest zbudowany na generatorach, więc nie są one odrębne
— Alexander Mills

Odpowiedzi:


46

yieldmożna uznać za element składowy await. yieldprzyjmuje podaną wartość i przekazuje ją wywołującemu. Wzywający może wtedy zrobić cokolwiek zechce z tą wartością (1). Później wywołujący może zwrócić wartość do generatora (via generator.next()), która staje się wynikiem yieldwyrażenia (2) lub błędu, który będzie wyglądał jak wyrzucony przez yieldwyrażenie (3).

async- awaitmożna uznać za używane yield. W (1) rozmówcy (czyli async- awaitkierowcy - podobna do funkcji, którą pisał) będą zawijać wartość w obietnicy stosując podobny algorytm new Promise(r => r(value)(uwaga, nie Promise.resolve , ale to nie jest wielka sprawa). Następnie czeka na spełnienie obietnicy. Jeśli spełnia, przekazuje spełnioną wartość z powrotem w (2). Jeśli odrzuca, zgłasza powód odrzucenia jako błąd w (3).

Więc użyteczność async- awaitjest tą maszynerią, która używa yielddo rozpakowania uzyskanej wartości jako obietnicy i przekazania jej ustalonej wartości z powrotem, powtarzając się, aż funkcja zwróci swoją ostateczną wartość.


1
sprawdź tę odpowiedź stackoverflow.com/a/39384160/3933557, która zaprzecza temu argumentowi. async-await wygląda podobnie do yield, ale używa łańcucha obietnic pod maską. Udostępnij, jeśli masz jakiś dobry zasób mówiący „async-await można uznać za używający yieldu”.
— Samarendra

1
Nie jestem pewien, jak przyjmujesz tę odpowiedź jako „zaprzeczającą temu argumentowi”, ponieważ mówi ona to samo, co ta odpowiedź. > W międzyczasie transpilery, takie jak Babel, pozwalają pisać async / await i konwertować kod do generatorów.
— Arnavion

mówi, że babel konwertuje się na generatory, ale to, co mówisz, to „plon może być uważany za element składowy await”, a „async-await może być traktowane jako użycie yield.”. co jest niezgodne z moim zrozumieniem (może ulec poprawie). async-await wewnętrznie używa łańcuchów obietnic, jak wspomniano w tej odpowiedzi. chcę zrozumieć, czy czegoś mi brakuje, czy mógłbyś podzielić się swoimi przemyśleniami na ten temat.
— Samarendra

Ta odpowiedź nie oznacza, że ​​wszystkie silniki ES na całym świecie wewnętrznie realizują obietnice za pomocą generatorów. Niektórzy mogą; niektórzy mogą nie; nie ma znaczenia dla pytania, na które jest to odpowiedź. Niemniej jednak sposób, w jaki obiecuje praca, można zrozumieć, używając generatorów ze szczególnym sposobem napędzania generatora i to wyjaśnia ta odpowiedź.
— Arnavion

45

Okazuje się, że istnieje bardzo ścisły związek między async/ awaita generatorami. I wierzę async/ awaitzawsze będzie budowany na generatorach. Jeśli spojrzysz na sposób transpozycji Babel async/ await:

Babel bierze to:

this.it('is a test', async function () {

    const foo = await 3;
    const bar = await new Promise(resolve => resolve('7'));
    const baz = bar * foo;
    console.log(baz);

});

i zamienia to w to

function _asyncToGenerator(fn) {
    return function () {
        var gen = fn.apply(this, arguments);
        return new Promise(function (resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function (value) {
                        return step("next", value);
                    }, function (err) {
                        return step("throw", err);
                    });
                }
            }

            return step("next");
        });
    };
}


this.it('is a test', _asyncToGenerator(function* () {   // << now it's a generator

    const foo = yield 3;    //  <<< now it's yield, not await
    const bar = yield new Promise(resolve => resolve(7));
    const baz = bar * foo;
    console.log(baz);

}));

ty to Oblicz.

To sprawia, że ​​wygląda na to, że asyncsłowo kluczowe jest po prostu tą funkcją opakowującą, ale jeśli tak jest, to po awaitprostu zostanie zamienione w yield, prawdopodobnie będzie trochę więcej na obrazie później, gdy staną się natywne.

Możesz zobaczyć więcej wyjaśnień tutaj: https://www.promisejs.org/generators/


2
NodeJS ma teraz natywny async / czekaj przez chwilę, bez generatorów: codeforgeek.com/2017/02/...
— Bram

3
Natywna implementacja @Bram całkowicie wykorzystuje generatory pod maską, to samo, po prostu wyodrębnione.
— Alexander Mills,

3
Nie sądzę. Async / await jest natywnie zaimplementowany w silniku V8. Generatory, w których funkcja ES6, async / await to ES7. Była to część wersji 5.5 silnika V8 (używanego w Node): v8project.blogspot.nl/2016/10/v8-release-55.html . Możliwe jest transpile ES7 async / await do generatorów ES6, ale w nowych wersjach NodeJS nie jest to już potrzebne, a wydajność async / await wydaje się nawet lepsza niż generatorów: medium.com/@markherhold/ ...
— Bram

1
async / await używa generatorów, aby robić swoje
— Alexander Mills,

1
@AlexanderMills, czy możesz udostępnić jakieś legalne zasoby, które mówią, że async / await używa wewnętrznie generatorów? sprawdź ten plik ans stackoverflow.com/a/39384160/3933557, który zaprzecza temu argumentowi. Myślę, że tylko dlatego, że Babel używa generatorów, nie oznacza to, że jest zaimplementowany podobnie pod maską. Wszelkie przemyślenia na ten temat
— Samarendra

28

jaka jest różnica między awaitsłowem kluczowym a yieldsłowem kluczowym?

Słowo awaitkluczowe ma być używane tylko w async functions, podczas gdy yieldsłowo kluczowe ma być używane tylko w generatorze function*. I one są oczywiście różne - jeden zwraca obietnice, drugi zwraca generatory.

Czy awaitzawsze zmienia coś w obietnicę, a yieldnie daje takiej gwarancji?

Tak, awaitwezwie Promise.resolveoczekiwaną wartość.

yield po prostu zwraca wartość poza generatorem.


Drobny problem, ale jak wspomniałem w mojej odpowiedzi, specyfikacja nie używa Promise.resolve (wcześniej), używa PromiseCapability :: resolution, który jest dokładniej reprezentowany przez konstruktor Promise.
— Arnavion

@Arnavion: Promise.resolveużywa dokładnie tego samego, new PromiseCapability(%Promise%)co specyfikacja async / await używa bezpośrednio, po prostu pomyślałem, że Promise.resolvelepiej to zrozumieć.
— Bergi

1
Promise.resolvema dodatkowe zwarcie „IsPromise == true ?, a następnie zwraca tę samą wartość”, którego nie ma w trybie asynchronicznym. Oznacza to, że await pgdzie pjest obietnica, zwróci nową obietnicę, która się spełni p, podczas gdy Promise.resolve(p)powróci p.
— Arnavion

Och, tęskniłem za tym - myślałem, że to tylko w Promise.casti zostało wycofane ze względu na spójność. Ale to nie ma znaczenia, tak naprawdę nie widzimy tej obietnicy.
— Bergi

2
var r = await p; console.log(r);powinien zostać przekształcony do czegoś takiego jak :, p.then(console.log);podczas gdy pmoże zostać utworzony jako :, var p = new Promise(resolve => setTimeout(resolve, 1000, 42));więc błędem jest mówić „czekaj na wywołania Promise.resolve”, jest to inny kod, który jest całkowicie oddalony od wywoływanego wyrażenia „await” Promise.resolve, więc przekształcone awaitwyrażenie , tj. Promise.then(console.log)zostanie wywołany i wydrukowany 42.
— Dejavu,

16

tl; dr

Użyj async/ await99% czasu na generatorach. Czemu?

  1. async/ awaitbezpośrednio zastępuje najpopularniejszy przepływ pracy w łańcuchach obietnic, umożliwiając zadeklarowanie kodu tak, jakby był synchroniczny, znacznie upraszczając go.

  2. Generatory abstrahują przypadek użycia, w którym można wywołać serię operacji asynchronicznych, które zależą od siebie i ostatecznie będą w stanie „gotowym”. Najprostszym przykładem byłoby przeglądanie wyników, które ostatecznie zwracają ostatni zestaw, ale wywołujesz stronę tylko w razie potrzeby, a nie bezpośrednio po sobie.

  3. async/ awaitjest właściwie abstrakcją zbudowaną na generatorach, aby ułatwić pracę z obietnicami.

Zobacz bardzo szczegółowe wyjaśnienie Async / Await vs. Generators


5

Wypróbuj te programy testowe, które rozumiałem await/ asyncz obietnicami.

Program nr 1: bez obietnic nie działa po kolei

function functionA() {
    console.log('functionA called');
    setTimeout(function() {
        console.log('functionA timeout called');
        return 10;
    }, 15000);

}

function functionB(valueA) {
    console.log('functionB called');
    setTimeout(function() {
        console.log('functionB timeout called = ' + valueA);
        return 20 + valueA;
    }, 10000);
}

function functionC(valueA, valueB) {

    console.log('functionC called');
    setTimeout(function() {
        console.log('functionC timeout called = ' + valueA);
        return valueA + valueB;
    }, 10000);

}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

Program nr 2: z obietnicami

function functionA() {
    return new Promise((resolve, reject) => {
        console.log('functionA called');
        setTimeout(function() {
            console.log('functionA timeout called');
            // return 10;
            return resolve(10);
        }, 15000);
    });   
}

function functionB(valueA) {
    return new Promise((resolve, reject) => {
        console.log('functionB called');
        setTimeout(function() {
            console.log('functionB timeout called = ' + valueA);
            return resolve(20 + valueA);
        }, 10000);

    });
}

function functionC(valueA, valueB) {
    return new Promise((resolve, reject) => {
        console.log('functionC called');
        setTimeout(function() {
            console.log('functionC timeout called = ' + valueA);
            return resolve(valueA + valueB);
        }, 10000);

    });
}

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
    console.log('response called = ' + response);
});
console.log('program ended');

0

Pod wieloma względami generatory są nadzbiorem async / await. Obecnie async / await ma czystsze ślady stosu niż co , najpopularniejsza biblioteka oparta na generatorze async / await. Możesz zaimplementować własny styl async / await za pomocą generatorów i dodać nowe funkcje, takie jak wbudowane wsparcie dla yieldnon-obietnic lub zbudowanie go na podstawie obserwacji RxJS.

Krótko mówiąc, generatory zapewniają większą elastyczność, a biblioteki oparte na generatorach mają na ogół więcej funkcji. Ale async / await jest podstawową częścią języka, jest ustandaryzowany i nie zmieni się pod tobą, a do jego używania nie potrzebujesz biblioteki. Mam post na blogu zawierający więcej szczegółów na temat różnicy między async / await a generatorami.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.