我想在我的 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));
所以这就是我理解应该发生的事情:
test
被推入调用堆栈并开始执行。slowDbUpdate
函数,看到它返回一个 Promise,将 .then
方法中的回调推送到微任务队列中。fastCacheGet
函数,并将其推入队列。fastCacheGet
承诺解决了,但是事件循环没有看到第一个承诺已经解决,并且它的回调没有被调用。slowDbUpdate
函数解析,事件循环将其回调推入调用堆栈,然后将 fastCacheGet
回调推入堆栈,因为它已经解析。但是当我运行代码时,它的工作方式就像我想要的那样(即返回缓存的数据(编辑2:日志记录
{ cache: "fast", db: "slow" }
)并在10秒后记录“数据已更新”),而不是我认为应该的方式。我显然误解了 JS 的工作原理,非常感谢任何帮助!
谢谢!
编辑 1:我也忘了问这个问题,但我正在使用 Node.js 和 Express 后端。我想做的事情一开始就是一个好主意吗?可以这么说,这是我第一次尝试在 API 尚未完成所有工作的情况下发送回响应。我不认为这应该是一个问题,但是如果我开始在很多 API 中这样做/这个 API 被非常频繁地调用,会发生什么。我知道我必须在此类承诺的末尾添加一个
.catch
,因为万一它抛出错误,它会导致应用程序作为未处理的拒绝而崩溃,但是还有什么可能出错呢?
让我评论一下您所描述的步骤:
- 函数 test 被压入调用堆栈并开始执行。
正确
- JS 调用
函数,看到它返回一个 Promise,将slowDbUpdate
方法中的回调推送到微任务队列中。.then
在此阶段没有任何内容被放入微任务队列中。
then
回调“仅”由 Promise API 注册(作为 Promise 实例的私有信息)。
- 然后调用
函数,并将其推入队列。fastCacheGet
fastCachet
返回一个具有挂起状态的 Promise 实例,但这里没有任何内容放入队列中。 await
运算符具有以下效果:
fastCacheGet
返回)获取任务(作为私人信息),该任务将在 test
运算符下方的点恢复 await
函数。test
返回(!),并且它向主程序返回一个待处理的承诺。
- (这是我不确定的地方)1秒过去了,
承诺解决了,但是事件循环没有看到第一个承诺已经解决,并且它的回调没有被调用。fastCacheGet
我认为混乱的根源可能是您认为
await
会阻止任何进一步的执行。但是这是错误的。当执行 test()
运算符时,await
返回,然后主程序也完成(在调用 .then
注册回调之后),以便事件循环可以在所有创建的 Promise 仍处于待处理状态时愉快地完成其工作。
大约一秒过去后,
setTimeout
回调被放置在作业队列中(由setTimeout
API的主机实现),事件循环检测并执行它。对 resolve
的调用解决了快速承诺,它具有注册的 then
回调,并将该回调放置在微任务队列中。事件循环检测到该回调并执行它,以便大约一秒钟后您可以获得预期的输出。
10 秒过去,
函数解析,事件循环将其回调推入调用堆栈,然后将slowDbUpdate
回调推入堆栈,因为它已经解析。fastCacheGet
如上所述,在
fastCacheGet
函数执行暂停时,test
回调已经执行。当大约 10 秒过去并且 setTimeout
回调执行时,“慢速”承诺得到解决。它有一个挂起的函数上下文(test
),因此它将其恢复放在微任务队列上。事件循环检测并执行此操作,即恢复 test
执行上下文,并继续执行直至结束。当执行其 return
语句时,主要承诺(10 秒前返回)现在也已实现。