为什么这个异步函数会同步运行?
这是我的代码:
async function test() {
console.log('TEST');
}
function go() {
console.log('one');
test();
console.log('two');
}
go()
我已将该test函数标记为async,并且我没有await在go调用它的方法中使用,所以我希望输出是这样的:
one
two
TEST
但是输出是这样的:
one
TEST
two
在我的实际用例中,test是一个函数,它包装了一些我确实希望异步而不是按顺序发生的长处理逻辑。
为什么这里没有发生这种情况,我该如何解决?
回答
async函数体同步执行,直到第一条await语句。当其遇到出await表达它产生控制到调用函数和被挂起。await表达式后面的部分是异步完成的。
根据 MDN文档:
可以将 async 函数的主体视为由零个或多个 await 表达式拆分。顶级代码,直到并包括第一个 await 表达式(如果有),都是同步运行的。这样,没有 await 表达式的 async 函数将同步运行。但是,如果函数体内有 await 表达式,则 async 函数将始终异步完成。
每个 await 表达式之后的代码可以被认为存在于 .then 回调中。通过这种方式,通过函数的每个可重入步骤逐步构建一个承诺链。返回值形成链中的最后一个链接。
这可以通过以下没有 no 的示例观察到await,其中函数体是同步执行的:
async function testSync() {
console.log("Runs synchronously");
}
console.log(1);
testSync();
console.log(2);
这是一个等待的Promise 在 之后使行为异步的示例await:
async function testAsync() {
console.log("Runs synchronously until here");
await myPromise();
console.log("Runs asynchronously here");
}
function myPromise() {
return Promise.resolve();
}
console.log(1);
testAsync();
console.log(2);
这可以通过多种方式异步执行。您可以将函数包装在setTimeout调用中:
function test() {
console.log("I want to execute asynchronously");
}
console.log(1);
setTimeout(test, 0);
console.log(2);
或者你可以通过承诺回调来做到这一点:
function test(){
console.log("I want to run asynchronously");
}
console.log(1);
Promise.resolve().then(test).catch(e => console.error(e));
console.log(2);
同样可以使用queueMicrotask, docs here 完成:
function test(){
console.log("I want to run asynchronously");
}
console.log(1);
queueMicrotask(test);
console.log(2);
setTimeout和使用 Promise 回调的方法之间的区别在于它们如何排队等待执行。有两个任务队列,一个是其中初始程序的执行,任务队列setTimeout,setInterval,requestAnimationframe等回调被排队,而另一个是microtask队列,其中承诺回调进行排队。
当你从排队的任务队列中的任务,有任务仍然留下,事件循环将检查microtask队列并执行所有从任务队列中占用的下一个任务之前。
这是一个演示这一点的片段:
function testTaskQueue(){
console.log("Enqueued in the task queue");
}
function testMicroTaskQueue(){
console.log("Enqueued in the microtask queue");
}
console.log(1);
setTimeout(testTaskQueue, 0);
Promise.resolve().then(testMicroTaskQueue);
queueMicrotask(testMicroTaskQueue);
console.log(2)
因此,根据这一点,您可以制作自己的异步包装器并将您的函数作为回调执行:
function makeAsync(callback, ...params) {
//enqueued intask queue
setTimeout(callback, 0, ...params);
//Or using microtasks
Promise.resolve().then(() => test(...params)).catch(console.error);
}
function test(...params) {
console.log("I want to run asynchronoulsy", ...params);
}
console.log(1)
makeAsync(test, 3);
console.log(2);