我发现如果不仔细思考的话,这可能会很棘手。异步函数到底什么时候返回给调用者?
我故意不将其制作成片段,以便读者可以猜测输出是什么:
打印出来的内容:
async function foo() {
console.log("COMING INTO foo");
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
console.log("Right before return");
return 123;
}
function takeIt(fn) {
console.log("STARTING");
foo();
console.log("Function returned");
}
takeIt(foo);
为什么?
视角一可以是:嗯,我们都知道异步函数会暂停
setTimeout
并休眠 3 秒,并且稍后不会返回...所以
STARTING
COMING INTO foo
Right before return
Function returned
视角二可以是:嗯,
foo()
返回123(或者不返回任何东西,这意味着返回undefined
),这是一个承诺......返回承诺是瞬时的,所以它是:
STARTING
Function returned
COMING INTO foo
Right before return
现在,如果我们在绘图中再添加一项调整:
async function foo() {
await Promise.resolve(678);
console.log("COMING INTO foo");
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
console.log("Right before return");
return 123;
}
function takeIt(fn) {
console.log("STARTING");
foo();
console.log("Function returned");
}
takeIt(foo);
并且可以有更多的观点......在我运行程序并得到正确答案之前,我猜测了所有 3 个案例打印的内容。但我并不知道它到底是如何运作的,但我做了一个猜测。有人可以详细说明它是如何工作的吗?如果需要,我会在几天后发布我的答案,说明我认为它到底是如何工作的。
问题是:
foo()
什么时候返回,确切地说,它是如何工作的?指导原则是什么?
一旦异步函数的代码运行到
Function returned
,异步函数就会将控制流返回给其调用者(即,在示例中记录 await
)。在此之前它不会产生回流。所以这里:
async function foo() {
console.log("COMING INTO foo");
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
调用
foo
将立即记录COMING INTO foo
,然后由于await
,控制流返回给foo
的调用者。
同样:
async function foo() {
await Promise.resolve(678);
console.log("COMING INTO foo");
await
首先出现,因此在这里,控制流在 foo
记录任何内容之前返回。
这里,“控制流被收回”与“向调用者返回 Promise”同义。
我的答案是,有时很难准确说出异步函数的行为方式,这里是转换:
async function foo() {
console.log("COMING INTO foo");
await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
console.log("Right before return");
return 123;
}
function takeIt(fn) {
console.log("STARTING");
foo();
console.log("Function returned");
}
takeIt(foo);
对于上面的代码,完全可以看成:
function foo() {
// any code before the first await goes here, which is executed immediately:
console.log("COMING INTO foo");
// when the first await is encountered, the whole thing becomes a promise
// and is returned to the caller:
return new Promise((resolve0, reject0) => {
const promise0 = new Promise((resolve) => {
setTimeout(resolve, 3000);
});
promise0.then(v0 => {
// the more await there are in the original code, the more "nested"
// `then` there will be. It does indeed become a nesting hell
console.log("Right before return");
// no matter how deep the nesting is, the last returned value
// is called with resolve0
resolve0(123);
});
});
}
所以想法是:一些代码将立即同步执行,但在
await
的第一个点,它突然变成一个巨大的承诺,其最终解析值将是异步函数的最后一行。所以这个“巨大的promise”就像漂浮在空中,等待着被调用和再次调用(我们可以把它想象成处理程序和处理程序被一次又一次地调用,并且是深深嵌套的)。
在async函数中,所有从await中获取值的变量就好像它们在同一作用域中一样,但是在转换后的代码中,then处理程序可以使用外部作用域访问这些变量,因此当then handler 被调用时,它利用闭包来访问这些外部作用域变量。
这个问答的目的是为了让我们准确地了解正在发生的事情,因为我发现一些程序员,包括我自己,在了解这一点之前,对正在发生的事情有一个模糊的想法,但无法真正说出,并且在没有真正理解的情况下编写代码这可能不是一个好的做法。