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 .

Alexander Mills
źródło
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ść.

Arnavion
źródło
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/

Alexander Mills
źródło
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.

Bergi
źródło
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

Jason Sebring
źródło
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');
Kamal Kumar
źródło
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.

vkarpov15
źródło