javascript - 关于 JavaScript 中 Promise 的一些问题
问题描述
我对 JavaScript 中 Promise 的行为感到困惑,并且有一些问题,有人可以帮我弄清楚吗?
这是创建 Promise 的两种常用方法。
function promise(){ return new Promise((resolve, reject) => { resolve()}); } let promise = new Promise(function(resolve, reject){ resolve(); });
第一个函数创建一个 Promise 对象,但在我们调用该函数之前不会调用它。相比之下,第二条语句创建了一个 Promise 对象并立即调用它。我对吗?
对于此功能:
function timeout(ms){ return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); });
我认为当我们调用超时函数时,它首先会创建一个异步函数
setTimeout
并将其推送到事件队列的尾部。在所有同步事件完成后,它将被调用并创建一个 Promise 对象。Promise 对象也会被推送到事件队列的尾部,在其他同步事件之后。所以似乎有两个事件循环,对吗?这两个陈述是否相等?
let promise = new Promise(function(resolve, reject){ resolve(); }); let promise = Promise.resolve();
为什么输出顺序如下?
setTimeout(function(){ console.log('3'); }, 0); Promise.resolve().then(function(){ console.log('2'); }); console.log('1'); // 1 // 2 // 3
那是一本书中的一个例子,作者解释如下:
setTimeout(fn, 0)
将在下一个事件循环开始时Promise.resolve()
调用, 将在当前事件循环结束时调用,console.log()
将立即调用。我对为什么
setTimeout
在下一个事件循环开始时调用该函数感到困惑。我认为它也是一个异步函数,将在当前事件队列的末尾推送,所以它应该在 Promise 对象之前调用。有人可以告诉我为什么我错了。
解决方案
第一个函数创建一个 Promise 对象,但在我们调用该函数之前不会调用它。相比之下,第二条语句创建了一个 Promise 对象并立即调用它。我对吗?
第一个函数只是一个函数。只要不调用它,就不会创建任何承诺。一旦调用它,与您作为替代的直接执行没有太大区别。
我认为当我们调用超时函数时,它首先会创建一个异步函数 setTimeout
setTimeout
未创建,该函数作为内置函数存在。你可以说它叫做.
...并将其推到事件队列的尾部。
推送到队列的不是函数setTimeout
本身。它是回调(resolve
在这种情况下),包括参数、超时时间和放在活动计时器列表中的唯一计时器 ID。
在所有同步事件完成后,它将被调用并创建一个 Promise 对象。
Promise 对象是在timeout
函数调用期间创建的。
传递给的回调new Promise
通常称为承诺构造函数回调,在执行的那一刻,new Promise
回调也被执行(同步)并创建承诺。
当同步代码完成时,即调用堆栈为空时,微任务队列首先被消耗。在这个阶段那里什么都没有,因此验证了任务队列。如果到那个时候一个活动的计时器已经到期,那么任务队列中就会有一个条目。
Promise 对象也会被推送到事件队列的尾部,在其他同步事件之后
Promise 对象没有放在任何队列中。当计时器到期时,排队的事件将作为一个新任务被触发,即resolve
函数将被调用,这反过来将解决承诺,这反过来会将条目放入微任务队列中,每个then
回调一个条目,并且await
-相关的影响。在从任务队列中提取任何其他任务之前,微任务队列将在同一个(宏)任务中处理。
所以似乎有两个事件循环,对吗?
至少两个;可以有更多。例如,在浏览器上下文中,可能还有另一个队列用于处理有关 DOM 元素突变的事件。另请参阅此答案,了解一些不同的规范对此有何评论。
- 这两个陈述是否相等?
几乎是的;Promise.resolve()
简称new Promise(r => r())
为什么输出顺序如下?
setTimeout
涉及任务队列,而.then
涉及微任务队列,总是在任务队列被处理之前被消耗;至少这是当前实现中的共识。
附录
以下是对以下代码的事件顺序的一些说明:
function timeout(ms){
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
首先解析代码并创建一个任务来执行它:
任务1
- 定义函数
timeout
(以函数为值的提升变量) timeout
使用参数 100 调用- 创建 promise 对象并同步调用构造函数回调。这两个参数由系统提供。它们是我们调用的内部函数,当 Promise 应该解决为已完成或已拒绝状态(由我们决定)时。
setTimeout
叫做。- 传递给的参数
setTimeout
用于在内部计时器列表中创建一个条目。 - 返回计时器的 ID,但您的代码不使用返回值。
- 创建的 promise 对象返回给调用者
timeout
- 调用者调用
then
这个 promise 的方法,传递给它一个任意函数(带有 的函数console.log
) - Promise 内部实现将此函数存储在队列(而不是事件队列)中,以供以后执行
- 到达代码末尾,调用栈为空
- 检查微任务队列是否有任何条目,但它也是空的
在后台:
- 计时器到期并将条目推送到任务队列。这是对传递给的函数的挂起调用
setTimeout
,即resolve
在我们的例子中。这究竟是如何发生的取决于实现。本质是在某个时刻任务队列有这个任务可以被消费。
任务 2
resolve
找到并处理任务队列中的条目(调用)resolve
叫做- 承诺内部(实现此
resolve
功能)将承诺标记为已解决 - 承诺内部在微任务队列中创建条目:每个
then
回调(或await
)存储在其内部队列中。在这种情况下,只有一个这样的条目:传递给then
代码中唯一方法的匿名函数。 - 调用堆栈为空(它只是对 的调用
resolve
) - 检查微任务队列是否有任何条目,其中有一个:
任务 2,微任务 1
- 微任务队列中的单个条目被消费
- 匿名函数被调用
console.log
被执行- 实现
console
产生输出 - 调用栈为空
- 微任务队列中没有更多条目
任务队列中没有更多条目。系统继续检查任务队列中的新条目。
推荐阅读
- javascript - MAC M1 安装“sharp”模块出现问题
- c# - for循环中的C#ADO.Net事务不插入所有记录而只插入一条记录
- sms - Jasmin SMPP 服务器绑定问题:ESME_RBINDFAIL ({'name': 'ESME_RBINDFAIL', 'description': 'Bind Failed'})
- python - 如何使用python编辑.txt文件,我想在每个数字后粘贴一个特定的字符串
- python - 每 2 分钟在 Web 服务器上执行一个脚本/文件(Python、.exe)
- c++ - 视觉工作室 C++ 上的 strchrnul?
- r - 具有错误维度的混淆矩阵
- typescript - 在由 makefile 提供服务的 WSL Docker 上运行的 PhpStorm (Win) TypeScript 中进行调试
- javascript - node js(javascript)代码不调用函数,而是将代码显示为文本
- text-mining - 向 fugashi 词典添加新词