Я всегда думал, что async/await
— это просто синтаксический сахар вокруг Promises
, и прежде чем его выполнить, движок JavaScript:
await
в .then()
catch {}
в .catch()
finally {}
в .finally()
Но потом в статье MDN об await я прочитал это await
приостанавливает выполнение окружающих
Это принципиально другое. Я придумал пример тестирования этого.
const promise = new Promise((res, rej) => {
setTimeout(() => {
console.info('promise resolves');
res();
}, 2000);
});
async function asyncFunc() {
if (Math.random() > 0.5) {
await promise;
console.info('#1 this executes after a delay 50% times');
}
else {
console.info('#2 this executes immediately 50% times');
}
console.info('this should not wait for promise to resolve but it does');
}
asyncFunc();
Приведенный выше код подтверждает утверждение о том, что выполнение кода фактически приостанавливается, потому что, если мы попытаемся перевести его в «обещанную» версию, у нас будет другой код, а именно оператор console.info
, находившийся за пределами оператора if/else
, должен быть продублирован, чтобы реплицировать async/await
поведение
const promise = new Promise((res, rej) => {
setTimeout(() => {
console.info('promise resolves');
res();
}, 2000);
});
function promiseFunc() {
if (Math.random() > 0.5) {
promise.then(() => {
console.info('#1 this executes after a delay 50% times')
console.info('this code, that was outside of if/else either goes here')
})
}
else {
console.info('#2 this executes immediately 50% times');
console.info('or here, based on the result of if')
}
}
promiseFunc();
Из-за этого у меня возникает ощущение, что Promise
и async/await
либо немного разные вещи, либо перевод кода крайне нелогичен.
УПД. С моей точки зрения, интуитивный перевод приведенного выше асинхронного кода должен выглядеть так.
const promise = new Promise((res, rej) => {
setTimeout(() => {
console.info('promise resolves');
res();
}, 2000);
});
function promiseFunc() {
if (Math.random() > 0.5) {
promise.then(() => {
console.info('#1 this executes after a delay 50% times')
})
}
else {
console.info('#2 this executes immediately 50% times');
}
console.info('this is outside of if/else block and should be executed regardless of await keyword above');
}
promiseFunc();
Вопрос в том:
Тот факт, что этот блок выполняется по обоим путям, не означает, что он не следует преобразованию № 1, которое вы перечислили. Просто только с промисами это невозможно выразить без условной оценки этого блока в отдельных продолжениях из-за поведения ветвления.
«Синтаксический сахар» — такой бессмысленный термин. Но если что-то async/await и является «сахаром» в функциях генератора, то не обещания. evertpot.com/syntactic-sugar . Но вы правы, они не одинаковы.
Постарайтесь не делать его слишком сложным. await
ожидает результата обещания, прежде чем продолжить. Это довольно интуитивно понятно... вам просто нужно подождать, пока сработает какая-то функция, сделать что-нибудь и вернуть результат, прежде чем двигаться дальше. async
просто заставляет функцию возвращать обещание и разрешает использовать await
внутри нее.
@Pointy, но если весь код после await
поместить в then()
, он также скопируется в else
? Разве это не выглядит странно?
@fires3as0n в этом весь смысл await
. На самом деле это не «пауза», это похоже на то, как если бы функция выполнялась как синхронная функция.
@fires3as0n также else
не имеет значения, потому что ветка if
уже занята. else
может и не быть.
Тот факт, что async/await
является синтаксическим сахаром для работы с промисами, не требует, чтобы где-либо происходило лексическое преобразование из async/await
в .then
. Это не одно и то же. Например, вы можете получить правильные трассировки стека с помощью await
.
@Пол, да, это важный момент. Удобно подумать о преобразовании await
кода в .then()
, но на самом деле это не совсем так. Это больше похоже на то, как работают функции-генераторы, но даже в этом случае лучше просто «довериться Силе» и кодировать с await
так, как будто это семантически вводит «паузу».
@Pointy, если это не пауза, ее следует перевести в версию Promise перед выполнением, а рабочий перевод выглядит как другой код, вот что сбивает с толку. Также вы сначала говорите, что «на самом деле это не «пауза»», а затем сразу же «кодируете с ожиданием, как если бы оно семантически налагало «паузу». - так это пауза и все-таки нет?
@fires3as0n опять же, это больше похоже на работу генераторов. await
похож на yield
под одеялом. Так что это что-то вроде .then()
, но не совсем. На самом деле, как я уже сказал, лучше всего верить, что это работает, и как только вы к этому привыкнете, вы сможете углубиться в детали.
Если он действительно делает паузу, можем ли мы сказать, что async/await и промисы — это не одно и то же?
Нет, вы не можете заметить разницу. Перевод действительно больше похож на ваш второй фрагмент кода, хотя, конечно, компилятору все равно, дублирует ли он код (или на самом деле просто дважды ссылается на один и тот же код). Обратите внимание, что даже программисту вам не нужно дублировать код для потоков условных обещаний.
Если он на самом деле преобразуется в версию Promise перед выполнением, почему он игнорирует блоки кода и распространяется за их пределы?
Он не игнорирует все блоки кода. Он их переводит.
Полная цитата из MDN на самом деле звучит так: «… приостанавливает выполнение окружающей функции async
». Он не только приостанавливает выполнение блока if
.
Да, перевод управляющих структур из кода await
в код .then()
не всегда интуитивно понятен, особенно когда речь идет о циклах или try
/catch
. Но в этом вся суть синтаксиса async
/await
— вам больше не нужно писать эти сложные цепочки .then()
, и вам не нужно ничего переводить или даже думать о переводе — вы просто используете обычный поток управления . структуры в асинхронном коде, к которым вы привыкли из синхронного кода.
Выполняет ли компилятор сложный перевод «под капотом» или использует другую, более эффективную модель выполнения, не имеет значения и на самом деле не наблюдаемо.
Интересный. Итак, мы можем сказать, что JS-движок действительно выполняет перевод, и делает это таким образом, что при выполнении результирующего кода он должен вести себя так, как если бы все исходное выполнение асинхронной функции было приостановлено до тех пор, пока не будет выполнено обещание. Что действительно может привести к дублированию кода, показанному в примере 2. (где дублирование кода рассматривается с точки зрения потока выполнения, а не того, как движок работает или делает это внутри себя)
Да. «Он должен вести себя так, как будто все исходное выполнение асинхронной функции было приостановлено до тех пор, пока не будет выполнено обещание», — вот в чем весь смысл. Для программиста JavaScript не имеет значения, как он это делает, и на самом деле детали часто меняются при реализации новых оптимизаций.
@Bergi Спасибо за ответ, я все еще думаю, что понимание того, как инструменты, которые вы используете, работают внутри, имеет большое значение для хорошего инженера.
@fires3as0n Конечно, но вы не можете ожидать, что их внутренняя работа будет интуитивно понятной :-) Кроме того, не все среды одинаковы, и вы не спрашивали о конкретном инструменте. Babel делает это совсем иначе (с реальным преобразованием исходного кода), чем современный JS-движок, который действительно способен приостанавливать выполнение функции, а затем возобновлять его позже, очень похоже на то, как доходность работает в функциях-генераторах. Это гораздо эффективнее, чем выполнять преобразования исходного кода и создавать огромное количество сложных замыканий.
await
фактически помещает весь последующий код функции в.then()
. Это выходит за рамки блока.