Является ли асинхронное ожидание по-настоящему неблокирующим в браузере?
Я играю с функцией в SPA, используя TypeScript и native Promises, и я замечаю, что даже если я реорганизую долговременную функцию в функцию async, возвращающую обещание, пользовательский интерфейс по-прежнему не отвечает.
Итак, мои вопросы:
-
Как именно новая функция async/await помогает избежать блокировки пользовательского интерфейса в браузере? Существуют ли какие-либо специальные дополнительные шаги, которые нужно предпринять при использовании async/wait, чтобы фактически получить отзывчивый интерфейс?
-
Может ли кто-нибудь создать скрипку, чтобы продемонстрировать, как async/await помогает сделать пользовательский интерфейс отзывчивым?
-
Как async/await относятся к предыдущим асинхронным функциям, таким как setTimeout и XmlHttpRequest?
Ответы
Ответ 1
await p
назначает выполнение остальной части вашей функции, когда обещает p
. Это все.
async
позволяет использовать await
. Это (почти) все, что он делает (это также завершает ваш результат в обещании).
Вместе они делают неблокирующий код прочитанным как более простой код блокировки. Они не разблокируют код.
Для чувствительного пользовательского интерфейса выгружайте интенсивную работу процессора в поток worker и передавайте ему сообщения:
async function brutePrime(n) {
function work({data}) {
while (true) {
let d = 2;
for (; d < data; d++) {
if (data % d == 0) break;
}
if (d == data) return self.postMessage(data);
data++;
}
}
let b = new Blob(["onmessage =" + work.toString()], {type: "text/javascript"});
let worker = new Worker(URL.createObjectURL(b));
worker.postMessage(n);
return await new Promise(resolve => worker.onmessage = e => resolve(e.data));
}
(async () => {
let n = 700000000;
for (let i = 0; i < 10; i++) {
console.log(n = await brutePrime(n + 1));
}
})().catch(e => console.log(e));
Ответ 2
async
- более элегантный способ структурирования асинхронного кода. Он не позволяет создавать новые возможности; это просто лучший синтаксис, чем обратные вызовы или promises.
Итак, async
нельзя использовать для "создания чего-то асинхронного". Если у вас есть код, который должен выполнять большую часть обработки на основе ЦП, async
не будет волшебно заставлять пользовательский интерфейс реагировать. Что вам нужно сделать, это использовать что-то вроде веб-работников, которые являются надлежащим инструментом для перевода работы, связанной с ЦП, на фоновый поток в порядке чтобы сделать пользовательский интерфейс отзывчивым.
Ответ 3
JavaScript является однопоточным и работает в том же потоке, что и пользовательский интерфейс. Таким образом, весь код JavaScript блокирует пользовательский интерфейс. Как упоминалось другими веб-рабочими, можно использовать для запуска кода в других потоках, но у них есть ограничения.
Разница между асинхронными функциями и регулярными заключается в том, что они возвращают обещание. Используя обратный вызов, вы можете отложить выполнение кода, который обрабатывает результат вызова функции и тем самым позволяет пользовательскому интерфейсу выполнять некоторую работу. Следующие три примера имеют тот же эффект:
async function foo() {
console.log("hi");
return 1;
}
foo().then(result => console.log(result))
console.log("lo");
function foo() {
console.log("hi");
return 1;
}
Promise.resolve(foo()).then(result => console.log(result))
console.log("lo");
function foo() {
console.log("hi");
return 1;
}
const result = foo();
setTimeout(() => console.log(result));
console.log("lo");
Во всех трех случаях консольные журналы hi, lo, 1. Перед печатью 1 пользовательский интерфейс может обрабатывать ввод пользователя или нарисовать обновления. Причина 1, которая напечатана последним в первых двух случаях, заключается в том, что обратные вызовы для promises не выполняются немедленно.
await
позволяет сделать это без обратных вызовов:
async function foo() {
console.log("hi");
return 1;
}
async function bar() {
const result = await foo();
console.log(result);
}
bar();
console.log("lo");
Это также напечатает hi, lo, 1. Как и обратный вызов для обещания, код после await
никогда не выполняется сразу.