Разница между async / await и выходом 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, да, но async await построен на основе генераторов, поэтому они не отличаются друг от друга
Александр Миллс

Ответы:

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 и преобразовывать код в генераторы.
Arnavion
в нем говорится, что babel конвертируют в генераторы, но вы говорите, что «yield может считаться строительным блоком await» и «async-await можно рассматривать как использование yield». что не соответствует моему пониманию (подлежит исправлению). async-await внутренне использует цепочки обещаний, как указано в этом ответе. Я хочу понять, если я чего-то упускаю, не могли бы вы поделиться своими мыслями по этому поводу.
Самарендра
Этот ответ не утверждает, что все движки ES во всем мире внутренне реализуют обещания с использованием генераторов. Некоторые могут; некоторые не могут; это не имеет отношения к вопросу, на который это ответ. Тем не менее, способ работы обещаний можно понять, используя генераторы с определенным способом управления генератором, и именно это объясняет этот ответ.
Arnavion
45

Что ж, оказывается, что существует очень тесная связь между async/ awaitи генераторами. И я верю async/ awaitвсегда буду строить на генераторах. Если вы посмотрите на то, как Babel передает 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 уже некоторое время имеет собственный async / await, без генераторов: codeforgeek.com/2017/02/…
Брэм,
3
Собственная реализация @Bram абсолютно использует генераторы под капотом, то же самое, только абстрагированное.
Александр Миллс
3
Я так не думаю. Async / await изначально реализован в движке V8. Генераторы, в которых функция ES6, async / await - это 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 использует генераторы внутри? проверьте этот ответ stackoverflow.com/a/39384160/3933557, что противоречит этому аргументу. Я думаю, что тот факт, что Babel использует генераторы, не означает, что он аналогичным образом реализован внутри. Любые мысли по этому
поводу
28

какая, черт возьми, разница между awaitключевым словом и yieldключевым словом?

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

Кто awaitвсегда в первую очередь что - то в обещание, в то время как yieldне дает такой гарантии?

Да, awaitвызовет Promise.resolveожидаемую стоимость.

yield просто дает значение вне генератора.

Берги
источник
Незначительный нюанс, но, как я уже упоминал в своем ответе, в спецификации не используется Promise.resolve (он использовался ранее), он использует PromiseCapability :: resolve, что более точно представлено конструктором Promise.
Arnavion,
@Arnavion: Promise.resolveиспользует то же самое, new PromiseCapability(%Promise%)что и спецификация async / await, я просто подумал, Promise.resolveчто лучше понять.
Bergi
1
Promise.resolveимеет дополнительное короткое замыкание «IsPromise == true? затем вернуть то же значение», которого нет в async. То есть, await pwhere pis a промис вернет новое обещание, которое будет разрешено в p, тогда как Promise.resolve(p)будет возвращено p.
Arnavion
О, я пропустил это - я думал, что это только в Promise.castи не рекомендуется по соображениям согласованности. Но это не имеет значения, мы все равно не видим этого обещания.
Bergi
2
var r = await p; console.log(r);должен быть преобразован во что-то вроде:, p.then(console.log);хотя pможет быть создан как:, var p = new Promise(resolve => setTimeout(resolve, 1000, 42));поэтому неверно говорить « ожидание вызовов Promise.resolve», это какой-то другой код, который находится совсем далеко от выражения 'await', которое вызывает Promise.resolve, поэтому преобразованное awaitвыражение , то есть Promise.then(console.log)будет вызван и распечатан 42.
Dejavu
16

tl; dr

Используйте async/ await99% времени на генераторах. Почему?

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

  2. Генераторы абстрагируются от варианта использования, в котором вы вызываете серию асинхронных операций, которые зависят друг от друга и в конечном итоге будут в состоянии «выполнено». Самый простой пример - это листание результатов, которые в конечном итоге возвращают последний набор, но вы вызываете страницу только по мере необходимости, а не сразу по очереди.

  3. async/ awaitна самом деле является абстракцией, построенной на основе генераторов, чтобы упростить работу с обещаниями.

См. Очень подробное объяснение Async / Await и генераторов

Джейсон Себринг
источник
5

Попробуйте эти тестовые программы, которые я привык понимать await/ asyncс обещаниями.

Программа no1: без обещаний она не запускается последовательно

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. Прямо сейчас async / await имеет более чистую трассировку стека, чем co , самая популярная библиотека на основе async / await-генераторов. Вы можете реализовать свой собственный вариант async / await с помощью генераторов и добавить новые функции, такие как встроенная поддержка для yieldне обещаний или создание его на наблюдаемых RxJS.

Короче говоря, генераторы дают вам большую гибкость, а библиотеки на основе генераторов обычно имеют больше функций. Но async / await - это основная часть языка, он стандартизирован и не будет меняться под вашим руководством, и вам не нужна библиотека для его использования. У меня есть сообщение в блоге с более подробной информацией о разнице между async / await и генераторами.

vkarpov15
источник