Разница между асинхронным/ожиданием и выходом ES6 с генераторами
Я только что читал эту фантастическую статью "Генераторы", и она ясно выделяет эту функцию, которая является вспомогательной функцией для обработки функций генератора:
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
Что ж, получается, что между 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 a generator
const foo = yield 3; // <<< now it 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
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
, чтобы развернуть полученное значение в качестве обещания и передать свое разрешенное значение обратно, повторяя, пока функция не вернет свое окончательное значение.
Ответ 3
Какая разница между ключевым словом await
и ключевым словом yield
?
Ключевое слово await
используется только в async function
s, а ключевое слово yield
используется только для генератора function*
s. И они, очевидно, тоже разные - один возвращает promises, другой возвращает генераторы.
Всегда ли await
превращает что-то в обещание, а yield
не дает такой гарантии?
Да, await
выведет Promise.resolve
на ожидаемое значение.
yield
просто дает значение вне генератора.
Ответ 4
ТЛ; др
Используйте async
/await
99% времени на генераторах. Почему?
async
/await
напрямую заменяет наиболее распространенный рабочий процесс цепочек обещаний, позволяя объявлять код, как если бы он был синхронным, что значительно упрощает его.
Генераторы абстрагируют случай использования, где вы бы назвали серию асинхронных операций, которые зависят друг от друга и в конечном итоге будут в состоянии "выполнено". Самый простой пример - листание результатов, которые в конечном итоге возвращают последний набор, но вы вызываете страницу только по мере необходимости, а не сразу по очереди.
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');
Ответ 6
Во многих отношениях генераторы являются расширенным набором асинхронных/ожидающих. Прямо сейчас async/await имеет более чистые следы стека, чем co, самая популярная библиотека на основе async/await-подобного генератора. Вы можете реализовать свой собственный вариант async/await с помощью генераторов и добавить новые функции, такие как встроенная поддержка yield
для не-обещаний или построение его на наблюдаемых объектах RxJS.
Короче говоря, генераторы дают вам больше гибкости, а библиотеки на основе генераторов обычно имеют больше возможностей. Но async/await является основной частью языка, он стандартизирован и не изменится под вас, и вам не нужна библиотека для его использования. У меня есть сообщение в блоге с более подробной информацией о разнице между async/await и генераторами.