Різниця між асинхронністю / очікуванням та виходом ES6 у генераторах


82

Я просто читав цю фантастичну статтю « Генератори », і вона чітко висвітлює цю функцію, яка є допоміжною функцією для обробки функцій генератора:

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);
    }
  }
}

що я припускаю, це більш-менш спосіб реалізації asyncключового слова за допомогою async/ await. Отже, питання полягає в тому, що якщо це так, то в чому полягає різниця між awaitключовим словом і yieldключовим словом? Чи awaitзавжди щось перетворює на обіцянку, тоді як yieldне дає такої гарантії? Це моє найкраще здогадування!

Ви також можете побачити, як async/ awaitподібно до yieldгенераторів у цій статті, де він описує асинхронні функції ES7 .


1
функція асинхронізації -> спільна програма. генератор -> ітератор, який використовує спільну програму для управління своїм внутрішнім механізмом ітерацій. await призупиняє програму, тоді як yield повертає результат із програми, яку використовує якийсь генератор
Девід Хаїм,

1
async/awaitне є частиною ES7. Будь ласка, прочитайте опис тегу.
Фелікс Клінг,

@david haim, так, але асинхронне очікування побудовано поверх генераторів, тому вони не відрізняються
Олександр Міллс

Відповіді:


46

yieldможна вважати будівельним елементом await. yieldприймає вказане значення та передає його абоненту. Тоді абонент може робити з цим значенням все, що забажає (1). Пізніше абонент може повернути значення генератору (через generator.next()), яке стає результатом yieldвиразу (2), або помилкою, яка, здається, викликана yieldвиразом (3).

async- awaitможна вважати використанням yield. В (1) викликає (тобто async- awaitводій - аналогічно функції ви опубліковану) буде обернути значення в обіцянці , використовуючи аналогічний алгоритм для new Promise(r => r(value)(примітка, НЕ Promise.resolve , але це не має великого значення ). Потім воно чекає, поки обіцянка вирішиться. Якщо воно виконується, воно передає виконане значення назад у (2). Якщо він відхиляє, він відкидає причину відхилення як помилку в (3).

Тож корисність async- awaitце цей механізм, який використовує yieldдля розгортання отриманого значення як обіцянки та передачі його вирішеного значення назад, повторюючи, поки функція не поверне своє остаточне значення.


1
перевірте цю відповідь stackoverflow.com/a/39384160/3933557, що суперечить цьому аргументу. async-await схожий на yield, але він використовує ланцюжок обіцянок під капотом. Будь ласка, поділіться, якщо у вас є якийсь хороший ресурс, каже: "async-await можна вважати використанням yield".
Самарендра

1
Я не впевнений, як ви сприймаєте цю відповідь як "суперечку цьому аргументу", оскільки вона говорить те саме, що і ця відповідь. > Тим часом такі транпілятори, як Babel, дозволяють писати async / await і перетворювати код у генератори.
Арнавіон

у ньому сказано, що babel перетворюється на генератори, але те, що ви говорите: "yield можна вважати будівельним блоком await", а "async-await можна вважати використанням yield". що не є правильним, на моє розуміння (підлягає виправленню). async-await внутрішньо використовує ланцюжки обіцянок, як зазначено у цій відповіді. я хочу зрозуміти, якщо чогось мені не вистачає, будь ласка, поділіться своїми думками з цього приводу.
Самарендра

Ця відповідь не стверджує, що всі двигуни ES у всьому світі реалізують обіцянки з використанням генераторів. Деякі можуть; деякі можуть ні; це не має значення для питання, на яке це відповідь. Тим не менше, спосіб обіцянки працює можна зрозуміти, використовуючи генератори з певним способом приводу генератора, і саме цим пояснюється ця відповідь.
Арнавіон

45

Ну, виявляється, існує дуже тісний взаємозв’язок між async/ awaitта генераторами. І я вірю async/ awaitбуду завжди будуватися на генераторах. Якщо ви подивитесь на те, як Бабель транспілює async/ await:

Бабель приймає це:

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);

});

і перетворює це на це

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);

}));

ви робите математику.

Це здається схожим на те, що asyncключове слово - це саме така функція обгортки, але якщо це так, то це awaitпросто перетворюється на yield, можливо, пізніше зображення буде трохи більше, коли вони стануть рідними.

Більше пояснень цьому можна побачити тут: https://www.promisejs.org/generators/


2
NodeJS має рідну систему асинхронізації / очікування деякий час, без генераторів: codeforgeek.com/2017/02/…
Bram,

3
Вбудована реалізація @Bram абсолютно використовує генератори під капотом, те саме, що просто абстраговано.
Олександр Міллс,

3
Я не думаю. Async / await спочатку реалізований у двигуні V8. Генератори, де функція ES6, асинхронна / очікувана, - ES7. Це було частиною випуску версії 5.5 двигуна V8 (який використовується в Node): v8project.blogspot.nl/2016/10/v8-release-55.html . Можна транспілювати ES7 async / await в генератори ES6, але з новими версіями NodeJS це більше не потрібно, і продуктивність async / await навіть здається кращою, ніж генератори: medium.com/@markherhold/…
Bram

1
async / await використовує генератори, щоб зробити свою справу
Олександр Міллс

1
@AlexanderMills, чи можете ви поділитися деякими законними ресурсами, в яких сказано, що async / await використовує генератори всередині? перевірте це ans stackoverflow.com/a/39384160/3933557, що суперечить цьому аргументу. Думаю, тільки тому, що Babel використовує генератори, це не означає, що він реалізований так само під капотом. Будь-які думки з цього
приводу

28

яка біса різниця між awaitключовим словом та yieldключовим словом?

awaitКлючове слово буде використовуватися тільки в async functionсек, в той час як yieldключове слово має використовуватися тільки в генератор function*s. І ці, очевидно, теж різні - один повертає обіцянки, інший - генератори.

Чи awaitзавжди щось перетворює на обіцянку, тоді як yieldне дає такої гарантії?

Так, awaitбуде викликати Promise.resolveочікуване значення.

yield просто дає значення поза генератором.


Незначна гніда, але, як я вже згадував у своїй відповіді, специфікація не використовує Promise.resolve (раніше), вона використовує PromiseCapability :: Resolution, який точніше представлений конструктором Promise.
Арнавіон

@Arnavion: Promise.resolveвикористовує точно те саме, new PromiseCapability(%Promise%)що специфікація async / await використовує безпосередньо, я просто вважав, що Promise.resolveце краще зрозуміти.
Бергі

1
Promise.resolveмає зайве коротке замикання "IsPromise == true? потім повернути те саме значення", якого не має асинхронізація. Тобто, await pде pобіцянка поверне нову обіцянку, яка вирішує p, тоді як Promise.resolve(p)повернеться p.
Арнавіон

О, я це пропустив - я думав, що це було лише в Promise.castі було припинено з міркувань послідовності. Але це не має значення, ми все одно насправді не бачимо цієї обіцянки.
Бергі

2
var r = await p; console.log(r);слід перетворити на щось на зразок:, p.then(console.log);хоча pможе бути створений як:, var p = new Promise(resolve => setTimeout(resolve, 1000, 42));тому неправильно говорити "await calls Promise.resolve", це якийсь інший код, зовсім далекий від виразу 'await', який викликає Promise.resolve, тому трансформований awaitвираз , тобто Promise.then(console.log)буде викликано та роздруковано 42.
Dejavu

16

tl; д-р

Використання async/ await99% часу над генераторами. Чому?

  1. async/ awaitбезпосередньо замінює найпоширеніший робочий процес ланцюжків обіцянок, що дозволяє оголошувати код так, ніби він синхронний, різко спрощуючи його.

  2. Генератори абстрагують варіант використання, коли ви називали б ряд асинхронних операцій, які залежать одна від одної і в кінцевому підсумку будуть у "готовому" стані. Найпростішим прикладом може бути пошук сторінок через результати, які в підсумку повертають останній набір, але ви викликаєте сторінку лише за потреби, а не відразу поспіль.

  3. async/ awaitнасправді є абстракцією, побудованою поверх генераторів, щоб полегшити роботу з обіцянками.

Див. Дуже поглиблене Пояснення щодо Async / Await vs. Generators


5

Спробуйте цю тестову програму, яку я звик розуміти await/ asyncз обіцянками.

Програма №1: без обіцянок вона не працює послідовно

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');

Програма №2: з обіцянками

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

Багато в чому генератори є надмножиною асинхронізації / очікування. Зараз async / await має чистіші сліди стека, ніж co , найпопулярніший lib на основі генератора async / await. Ви можете реалізувати свій власний смак асинхронності / очікування за допомогою генераторів та додати нові функції, такі як вбудована підтримка для yieldнеобіцяючих або побудова її на спостережуваних RxJS.

Отже, коротше, генератори надають вам більшу гнучкість, а бібліотеки на основі генераторів, як правило, мають більше можливостей. Але async / await - це основна частина мови, вона стандартизована і не зміниться під вас, і вам не потрібна бібліотека для її використання. У мене є допис у блозі з докладнішими відомостями про різницю між async / await та генераторами.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.