为什么无限递归异步函数不会导致堆栈溢出?

时间:2019-05-19 06:23:59

标签: javascript async-await

我在想,当异步函数递归地无限调用自己时会发生什么。我的想法是,它不会导致堆栈溢出。但是我无法确切指出为什么会这样。

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
    foo();
}

foo();

上面的代码无限打印“ foo”,而不会溢出堆栈。

我的想法是该代码在概念上类似于以下代码,它不会引起堆栈溢出,因为对foo()的递归调用位于回调内部,对foo()的原始调用将在返回之前返回那个。

const bar = () => {
    console.log("foo");
    foo();
}

const foo = () => {
    setImmediate(bar);
}

foo();

我正在寻找有关异步功能情况的确切答案。

2 个答案:

答案 0 :(得分:2)

您的代码不会产生堆栈溢出,因为在函数内调用foo时不会对其进行await编辑。如果您写await foo();,则应导致堆栈溢出。

请考虑以下两种情况:

案例1 在这里根据您的代码。从a()它将调用foo,而无需await。因此,当它调用foo()时会发生什么,因为它是async函数,它would be scheduled to run after the current execution resolves. Or even more precisely, it will be queued for later execution和立即a()也会从下一行继续。您可以看到输出a()首先结束,它不等待foo返回调用堆栈;

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
}

const a = async () => {
    const txt = await Promise.resolve("a");
    console.log(txt);
    foo();
    console.log("-- ENd of a() --");
}

a();

案例2 a()内部,它将用foo调用await。您可以看到a()正在等待foo()返回的输出,然后只有它会在下一行继续。

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
}

const a = async () => {
    const txt = await Promise.resolve("a");
    console.log(txt);
    await foo();
    console.log("-- ENd of a() --");
}

a();

答案 1 :(得分:2)

此功能是

的语法糖
const foo = () => 
  Promise.resolve(
    Promise.resolve("foo")
    .then(txt => {
      console.log(txt);
      foo();
    })
  );

foo();

此本身可以用更少的依赖项重写为

const foo = () =>
  queueMicrotask(() =>
    queueMicrotask(() => {
      console.log("foo");
      foo();
    })
  );
foo();

Window.queueMicrotask是一种非常新的方法,它为我们提供了一种触发queue a microtask操作的方法,该操作由Promise.resolve触发,因此await也会触发。
基本上,此操作在当前执行结束时但在当前事件循环结束之前推送微任务。

该算法的第六点是

  

将设置任务的脚本评估环境设置对象设置为空集。

这就是为什么您在这里没有堆栈溢出的原因。但是,由于您永远不会退出事件循环,因此您正在阻止浏览器。