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

17

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

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();

我正在寻找有关异步函数情况下发生的确切答案。


5
由于递归异步调用会等待当前执行完成后再创建新的堆栈帧,因此您不会耗尽堆栈空间。 - VLAZ
是的,你的解释是正确的。如果你使用 await foo() ,很可能会有内存泄漏的问题。 - Bergi
2个回答

5
这个函数是语法糖。
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 是一个相当新的方法,它为我们提供了一种触发 排队微任务 操作的方式,而 Promise.resolve 和 await 也会触发这个操作。
基本上,这个操作将微任务推到当前执行的末尾,但在当前事件循环结束之前。

算法的第六点如下:

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

这就是为什么你没有堆栈溢出的原因。然而,由于你从未退出事件循环,你阻塞了浏览器。


2

您的代码之所以没有引起堆栈溢出,是因为在函数内部调用 foo 时没有使用 await。如果写成 await foo(); 则会导致堆栈溢出。

考虑下面两种情况:

情况一 根据您的代码,在从 a() 调用 foo 时没有使用 await。那么当它调用 foo() 时,由于它是异步函数,它将“在当前执行完成后计划运行。更准确地说,它将被排队等待稍后执行”,并且立即执行了 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();

案例2a()函数中,它会使用await调用foo函数。你可以看到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
will be called in parallel thread”将在并行线程中调用,但实际上并没有并行线程 - 它会在当前执行完成后被安排运行。更准确地说,它将被排队等待后续执行 - 在递归调用之前,您可以有更多的队列条目需要执行。如果每个异步递归调用都在一个并行线程中,则首先,递归异步调用将永远不确定,其次,它们可能导致有效的分叉炸弹。 - VLAZ
@VLAZ,你是正确的。我知道JavaScript是“单线程”的,JavaScript中没有“并行线程”的概念。我只是觉得用这种方式解释会更容易理解。我已经更新了答案。但如果你有任何改进意见,请随时编辑。谢谢。 - Karan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接