【问题标题】:Why doesn't an infinitely recursive async function cause stack overflow?为什么无限递归异步函数不会导致堆栈溢出?
【发布时间】:2019-10-05 21:37:08
【问题描述】:

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

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

我正在寻找关于异步函数情况的确切答案。

【问题讨论】:

  • 这是因为递归异步调用将等待 当前 执行完成,然后再创建一个新的堆栈帧。因此,您不会用完堆栈空间。 stackoverflow.com/questions/39459236/…
  • 是的,你的解释是正确的。如果你愿意await foo(),你可能会发生内存泄漏。

标签: javascript async-await


【解决方案1】:

这个函数是

的语法糖
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 触发。
基本上,此操作在当前执行结束时推送微任务,但在当前事件循环结束之前。

算法第六点读取

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

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

【讨论】:

    【解决方案2】:

    您的代码不会产生堆栈溢出,因为当您在函数内部调用 foo 时,它不是 awaited。如果你写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();

    案例 2a() 内部,它将调用fooawait。您可以看到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();

    【讨论】:

    • "将在并行线程中调用" 没有并行线程 - 它是同一个线程,但会安排在当前执行解决后运行。或者更准确地说,它将排队等待稍后执行 - 在递归调用之前,您可以在队列中有更多的项目来执行。如果每个异步递归调用都是并行线程,那么首先,递归异步调用永远不会是确定性的 2. 它们可能导致有效的分叉炸弹。
    • @VLAZ,你是对的。我知道javascript是single threaded,在javascript中没有parallel thread这样的东西。我只是认为这样解释很容易。我已经更新了答案。但是您仍然有任何改进,请随时进行编辑。谢谢。
    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 2011-02-26
    • 2016-02-28
    • 2013-04-05
    • 2021-04-19
    相关资源
    最近更新 更多