Я всегда думал, что async / await в Javascipt - это просто синтаксис сахара, например писать этот кусок кода
let asyncVar = await AsyncFunc()
console.info(asyncVar)
//rest of code with asyncVar
эквивалентно написанию этого
AsyncFunc().then(asyncVar => {
console.info(asyncVar)
//rest of the code with asyncVar
})
Тем более, что вы можете вызвать await для некоторого случайного объекта, не являющегося обещанием, и он попытается вызвать его функцию then ()
Однако я попробовал этот кусок кода
let asyncFunc = function() {
return new Promise((resolve,reject) => {
setTimeout(_ => resolve('This is async'), 1000)
})
}
for(let i = 0; i < 5; i++) {
console.info('This is sync')
asyncFunc().then(val => console.info(val))
}
for(let i = 0; i < 5; i++) {
console.info('This is sync')
console.info(await asyncFunc())
}
Первый цикл выводит «Это синхронно» 5 раз, а затем «Это асинхронно» 5 раз, как и ожидалось. Второй цикл выводит «Это синхронно», «Это асинхронно», «Это синхронно», «Это асинхронно» и т. д.
Может ли кто-нибудь объяснить, в чем разница, или, другими словами, что именно async / await делает за кулисами?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


async / await - это новое поколение обещаний. По сути, await будет задерживать выполнение остальной части кода до разрешения await. Например.
async function getUser() {
const id = 'zzzz';
const user = await get(`http://getuser.xxx/{id}`);
console.info(user);
}
Единственная консоль печатается после выполнения функции get.
await делает то, что написано на коробке: он ожидает ответа вашей асинхронной функции:
let asyncFunc = () =>
new Promise((resolve,reject) => {
setTimeout(() => resolve("This is async"), 5000)
});
console.info("before await");
console.info(await asyncFunc());
console.info("after await");
Будет записан "before await", затем, через 5 секунд, будет записан "This is async", а затем "after await".
Это в основном заставляет асинхронную функцию вести себя синхронно.
@IvanJakab: тогда в чем конкретно ваш вопрос? Как это реализовано?
По отдельности да, async/await - синтаксический сахар для обещаний. Вам не хватает более широкого контекста: сами обещания присутствуют, потому что JS не мог приостановить выполнение функции до того, как появился await. Чтобы смоделировать этот эффект паузы, вы должны преобразовать всю синхронную функцию (в данном случае цикл for), а не только строку await.
Сначала мы должны развернуть цикл for в while (говоря о синтаксическом сахаре!), Затем мы можем переписать его в рекурсию (потому что до await мы могли «останавливаться» только между функциями, а не внутри них), затем мы можем перепрограммировать это рекурсивно только при выполнении обещания. Это ваш код, преобразованный из async/await в обещания:
let asyncFunc = function() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('This is async'), 1000)
});
};
function loop(i) {
if (i < 5) {
console.info('This is sync');
return asyncFunc().then(result => {
console.info(result);
return loop(++i);
});
}
}
loop(0);По сути, теперь у вас есть два типа функций: функции async и функции, отличные от async, и только функции async могут иметь в себе await (или вы получите сообщение «SyntaxError: await действительно только в асинхронной функции»). Это своего рода причина: чтобы «переписать» семантику await в семантику, отличную от await, вам нужно изменить всю функцию, а не только строки await. Это синтаксический сахар, но на уровне вся функция, а не на уровне операторов, как в примерах OP.
Обновлено: вы также можете в основном прочитать, что нужно сделать в исходном коде:
for(let i = 0; i < 5; i++) {
console.info('This is sync')
console.info(await asyncFunc())
}
Let a loop start with
ibeing0. Check thatiis less then5; if so, log "This is sync", and invokeasyncFunc(and wait for it to finish). Then, print out its result, increment the counter and continue the loop.
Несмотря на то, что код был радикально переработан относительно синтаксиса JavaScript, он по-прежнему очень похож на то, что я написал, не так ли? :)
На самом деле это многое объясняет, но вы говорите, что циклы for на самом деле являются рекурсивными функциями за кулисами? Это многое объяснило бы, поскольку я где-то читал, что for (let i = 0; i <5; i ++) фактически создает новый лексический контекст для переменной i на каждой итерации и копирует в него предыдущее значение
@IvanJakab: то, что они скрывают, полностью зависит от того, как браузер / движок их реализует.
Нет, скорее всего, это не рекурсивные функции. Я написал это, чтобы проиллюстрировать, какой код async/await был создан для замены, учитывая, что было сделано синтаксическое расширение. Единственный способ точно узнать, что на самом деле происходит за кулисами, - это прочитать исходный код браузера. Единственный способ получить достаточно четкое представление о том, что происходит с предполагается, - это прочитать спецификацию ECMAScript (начиная с здесь и здесь).
Похоже, вы ошибаетесь, что люди, использующие фразу «синтаксический сахар», просто означают, что это простой поиск и замена из кода обещания. Нет. Как должно быть ясно из моего примера, async/await сильно влияет на структуру кода, и довольно ошибочно рассматривать одно как макрос для другого. Однако, как вы можете видеть, всегда можно написать одно и то же в стиле обещаний и в стиле async / await, и последний, очевидно, легче читать - вот почему люди называют это «синтаксическим сахаром», поскольку вы можете переписать та же семантика в гораздо более легком для восприятия синтаксисе.
async/await может быть синтаксическим сахаром, но это больше, чем просто сахар вокруг then(). Намного лучшая аналогия для async/await - генераторы + обещания.. Если вы напишете генератор, вы заметите, что синтаксис почти такой же, как у async/await, но с использованием yield вместо await.
Например, вы можете написать генератор, который будет выглядеть почти так же, как ваш второй цикл for, и он будет давать вам такое же поведение:
let asyncFunc = function() {
return new Promise((resolve,reject) => {
setTimeout(_ => resolve('This is async'), 1000)
})
}
/* Generator Function */
function *gen(){
for(let i = 0; i < 5; i++) {
console.info('This is sync')
yield asyncFunc().then(console.info) // looks and acts a lot like await
}
}
/* iterate through the generator */
let it = gen()
function runGen(){
let p = it.next()
if (p.done) return
else p.value.then(runGen)
}
runGen()Но это делает @Cerbrus. await работает очень похоже на yield в генераторах. Он приостанавливает выполнение в этой точке функции - точно так же, как yield. Синтаксис выглядит так же; поведение очень близко к тому же. Вопрос состоит в том, чтобы спросить «что происходит за кулисами» и попытаться понять это с точки зрения синтаксического сахара. Я подумал, что это может быть полезно… явно не все так думают.
Что ж, я пытаюсь понять, что для тех, кто не знает, как работает await, генераторы могут быть еще более непонятными. Я не думаю, что вы упрощенный код ;-)
Мы явно по-разному читаем вопрос @Cerbrus. Похоже, OP прекрасно знает, как использовать async/await.
Как это сделать использовать, да. Но он явно спрашивает, как это происходит. работает, "что именно async / await делает за кулисами?" Все, что я сказал, это то, что генераторы могут быть еще более запутанными, если вы пытаетесь объяснить, что делает await.
Я понимаю вашу точку зрения @Cerbrus. Но если я спрашиваю, как работает await, и получаю ответ «await ожидает ответа», это мне ничего не говорит. Это не дает никакого представления о том, «что именно async / await делает за кулисами?». Если я спрашиваю, какой синтаксис заслаивается, я ищу нечто большее, чем «await ожидает ответа». И ответ «это заставляет асинхронную функцию вести себя синхронно», ну… это на самом деле неверно. Это заставляет их вести себя как генераторы, и это моя точка зрения. Они делают паузу в синхронных функциях. не могу. Итак, я даю ОП возможность сомневаться в этом.
генераторы и yield, вероятно, менее понятны большинству, чем async / await, поэтому использование yield и генераторов для объяснения async / await не поможет многим людям. Это похоже на объяснение значения испанской фразы говорящему по-английски с помощью французских слов.
В этом случае мне придется встать на сторону @MarkMeyer, этот ответ очень информативен и многое объясняет. Возможно, я не ясно выразился, но я хотел узнать, что такое синтаксис async / await, и я не понимаю, как я указал, что не смогу понять объяснение генератора. Спасибо за ваш ответ, у меня есть несколько плохих ответов, хотя есть один хороший ответ, который либо не совсем то же самое, либо действительно объясняет, как yield работает за кулисами
синтаксический сахар * для, ответ *, вовремя не заметил
Спасибо @IvanJakab.
Ваши первые два примера в основном эквивалентны. Но, угадайте, в этих двух примерах нельзя использовать цикл for. Не существует простого способа имитации оператора await внутри цикла for. Это потому, что взаимодействие цикла for (или цикла while) с async/await является более продвинутым, чем включение простого оператора .then(), как в ваших двух примерах.
await приостанавливает выполнение всей содержащей функции. Это приостановит даже петли for и while. Чтобы запрограммировать что-то подобное только с .then(), вы должны изобрести свою собственную конструкцию цикла, потому что вы не можете сделать это только с помощью цикла for.
Например, если у вас это с помощью await:
let asyncFunc = function() {
return new Promise((resolve,reject) => {
setTimeout(_ => resolve('This is async'), 1000)
})
}
async function someFunc() {
for(let i = 0; i < 5; i++) {
console.info('This is sync')
console.info(await asyncFunc())
}
}
И, если вам нужна аналогия с использованием только .then(), вы не можете использовать цикл for, потому что нет способа «приостановить» его без await. Вместо этого вам нужно разработать свой собственный цикл, который обычно включает вызов локальной функции и поддержание собственного счетчика:
function someFunc() {
let i = 0;
function run() {
if (i++ < 5) {
console.info('This is sync')
asyncFunc().then(result => {
console.info(result);
run();
});
}
}
run();
}
Или некоторые используют такую конструкцию .reduce() (особенно, если вы повторяете массив):
// sequence async calls iterating an array
function someFunc() {
let data = [1,2,3,4,5];
return data.reduce((p, val) => {
console.info('This is sync')
return p.then(() => {
return asyncFunc().then(result => {
console.info(result);
});
});
}, Promise.resolve());
}
Can someone explain what is the difference, or in other words, what exactly async/await does behind the scenes?
За кулисами интерпретатор Javascript приостанавливает дальнейшее выполнение функции с помощью оператора ожидания. Текущий контекст функции (состояние локальной переменной, точка выполнения и т. д.) Сохраняется, из функции возвращается обещание, и выполнение за пределами этой функции продолжается до тех пор, пока не будет выполнено или отклонено обещание, которое ожидалось. Если он разрешается, то выбирается то же состояние выполнения, и функция продолжает выполняться еще до следующего await и так далее, пока в конечном итоге функция не будет иметь больше операторов await и не будет больше выполнять (или перейдет к оператору return). .
Чтобы объяснить дальше, вот некоторые предыстории об асинхронной функции:
Когда вы объявляете функцию async, интерпретатор создает особый вид функции, которая всегда возвращает обещание. Это обещание отклоняется, если функция либо явно возвращает отклоненное обещание, либо если в функции есть исключение (исключение перехватывается интерпретатором и превращается в отклонение обещания, которое возвращает функция).
Обещание разрешается, если / когда функция возвращает нормальное значение или просто возвращается нормально, завершив свое выполнение.
Когда вы вызываете функцию, она начинает выполняться синхронно, как и любая обычная функция. Сюда входят любые циклы, которые могут быть в функции (как в цикле for в вашем примере). Как только функция встречает первый оператор await и ожидающее значение является обещанием, тогда функция возвращается в этой точке и возвращает свое обещание. Дальнейшее выполнение этой функции приостанавливается. Поскольку функция возвращает свое обещание, любой код после этого вызова функции продолжает выполняться. Некоторое время спустя, когда обещание, которое ожидалось внутри функции async, разрешается, в цикл событий вставляется событие, чтобы возобновить выполнение этой функции. Когда интерпретатор возвращается в цикл обработки событий и переходит к этому событию, выполнение функции возобновляется с того места, где оно было остановлено в строке сразу после оператора await.
Если есть какие-либо другие операторы await, ожидающие обещаний, они аналогичным образом вызовут приостановку выполнения функции до тех пор, пока ожидаемое обещание не будет разрешено или отклонено.
Прелесть этой способности приостанавливать или приостанавливать выполнение функции в середине курса заключается в том, что она работает в конструкциях цикла, таких как for и while, и это делает выполнение асинхронных операций в последовательности с использованием такого цикла намного проще для программирования, чем раньше. async и await (как вы обнаружили). Не существует просто аналогичного способа кодирования цикла секвенирования с использованием только .then(). Некоторые используют .reduce(), как показано выше. Другие используют вызов внутренней функции, как показано выше.
Спасибо за очень информативный ответ. Этот ответ аналогичен предыдущему, но, чтобы уточнить, вы говорите, что async / await - это не просто синтаксический сахар, и он фактически приостанавливает выполнение функции? Я знаю, что на самом деле это не имеет значения, и это зависит от интерпретатора, но просто из любопытства (с упором на node.js)
@IvanJakab - Да, это фактически приостанавливает выполнение функции. Для внешнего мира функция возвращает обещание в точке первого await. Внутри функции выполнение приостанавливается и возобновляется позже, когда ожидаемое обещание разрешится. async/await - это больше, чем просто синтаксический сахар. Это позволяет вам делать некоторые вещи проще, чем с .then(), как показывают мои примеры кода. await НЕ блокирует интерпретатор или цикл событий. Внешний мир (за пределами функции async) думает, что функция вернула неразрешенное обещание (потому что это так).
Я знаю, что это так, но, к сожалению, это не отвечает на мой вопрос