执行此代码时,调用堆栈、事件循环和微任务队列将如何表现?

问题描述 投票:0回答:1

我想在我的 API 中获取一些缓存数据,并进行数据库更新查询。我已经编写了代码,它工作得很好,但据我了解,它不应该。有人可以解释一下这里的一切是如何执行的吗?

const slowDbUpdate = () => {
    return new Promise((res, rej)=>{
        setTimeout(()=>{
            return res("DATA UPDATED");
        }, 10000);
    })
}

const fastCacheGet = () => {
    return new Promise((res, rej)=>{
        setTimeout(()=>{
            return res({ cache: "fast", db: "slow" });
        }, 1000);
    })
}

const test = async () => {
    try {
        slowDbUpdate().then(res => console.log(res));
        const data = await fastCacheGet();

        return data;
    } catch (error) {
        console.log("Error", error);
    }
}

编辑2: 我正在运行这个函数,我只是懒得把它写下来。

test().then(res => console.log(res));

所以这就是我理解应该发生的事情:

  1. 函数
    test
    被推入调用堆栈并开始执行。
  2. JS 调用
    slowDbUpdate
    函数,看到它返回一个 Promise,将
    .then
    方法中的回调推送到微任务队列中。
  3. 然后调用
    fastCacheGet
    函数,并将其推入队列。
  4. (这是我不确定的地方)1秒过去了,
    fastCacheGet
    承诺解决了,但是事件循环没有看到第一个承诺已经解决,并且它的回调没有被调用。
  5. 10 秒过去,
    slowDbUpdate
    函数解析,事件循环将其回调推入调用堆栈,然后将
    fastCacheGet
    回调推入堆栈,因为它已经解析。

但是当我运行代码时,它的工作方式就像我想要的那样(即返回缓存的数据(编辑2:日志记录

{ cache: "fast", db: "slow" }
)并在10秒后记录“数据已更新”),而不是我认为应该的方式。我显然误解了 JS 的工作原理,非常感谢任何帮助!

谢谢!

编辑 1:我也忘了问这个问题,但我正在使用 Node.js 和 Express 后端。我想做的事情一开始就是一个好主意吗?可以这么说,这是我第一次尝试在 API 尚未完成所有工作的情况下发送回响应。我不认为这应该是一个问题,但是如果我开始在很多 API 中这样做/这个 API 被非常频繁地调用,会发生什么。我知道我必须在此类承诺的末尾添加一个

.catch
,因为万一它抛出错误,它会导致应用程序作为未处理的拒绝而崩溃,但是还有什么可能出错呢?

javascript promise event-loop
1个回答
0
投票

让我评论一下您所描述的步骤:

  1. 函数 test 被压入调用堆栈并开始执行。

正确

  1. JS 调用
    slowDbUpdate
    函数,看到它返回一个 Promise,将
    .then
    方法中的回调推送到微任务队列中。

在此阶段没有任何内容被放入微任务队列中。

then
回调“仅”由 Promise API 注册(作为 Promise 实例的私有信息)。

  1. 然后调用
    fastCacheGet
    函数,并将其推入队列。

fastCachet
返回一个具有挂起状态的 Promise 实例,但这里没有任何内容放入队列中。
await
运算符具有以下效果:

  1. 承诺(由
    fastCacheGet
    返回)获取任务(作为私人信息),该任务将在
    test
    运算符下方的点恢复
    await
    函数。
  2. 函数
    test
    返回(!),并且它向主程序返回一个待处理的承诺。
  1. (这是我不确定的地方)1秒过去了,
    fastCacheGet
    承诺解决了,但是事件循环没有看到第一个承诺已经解决,并且它的回调没有被调用。

我认为混乱的根源可能是您认为

await
会阻止任何进一步的执行。但是这是错误的。当执行
test()
运算符时,
await
返回,然后主程序也完成(在调用
.then
注册回调之后),以便事件循环可以在所有创建的 Promise 仍处于待处理状态时愉快地完成其工作。

大约一秒过去后,

setTimeout
回调被放置在作业队列中(由
setTimeout
API的主机实现),事件循环检测并执行它。对
resolve
的调用解决了快速承诺,它具有注册的
then
回调,并将该回调放置在微任务队列中。事件循环检测到该回调并执行它,以便大约一秒钟后您可以获得预期的输出。

10 秒过去,

slowDbUpdate
函数解析,事件循环将其回调推入调用堆栈,然后将
fastCacheGet
回调推入堆栈,因为它已经解析。

如上所述,在

fastCacheGet
函数执行暂停时,
test
回调已经执行。当大约 10 秒过去并且
setTimeout
回调执行时,“慢速”承诺得到解决。它有一个挂起的函数上下文(
test
),因此它将其恢复放在微任务队列上。事件循环检测并执行此操作,即恢复
test
执行上下文,并继续执行直至结束。当执行其
return
语句时,主要承诺(10 秒前返回)现在也已实现。

© www.soinside.com 2019 - 2024. All rights reserved.