首页 > 解决方案 > 关于 JavaScript 中 Promise 的一些问题

问题描述

我对 JavaScript 中 Promise 的行为感到困惑,并且有一些问题,有人可以帮我弄清楚吗?

  1. 这是创建 Promise 的两种常用方法。

    function promise(){
        return new Promise((resolve, reject) => {
            resolve()});
    }
    
    let promise = new Promise(function(resolve, reject){
        resolve();
    });
    

    第一个函数创建一个 Promise 对象,但在我们调用该函数之前不会调用它。相比之下,第二条语句创建了一个 Promise 对象并立即调用它。我对吗?

  2. 对于此功能:

    function timeout(ms){
        return new Promise((resolve, reject) => {
            setTimeout(resolve, ms, 'done');
    });
    }
    
    timeout(100).then((value) => {
        console.log(value);
    });
    

    我认为当我们调用超时函数时,它首先会创建一个异步函数setTimeout并将其推送到事件队列的尾部。在所有同步事件完成后,它将被调用并创建一个 Promise 对象。Promise 对象也会被推送到事件队列的尾部,在其他同步事件之后。所以似乎有两个事件循环,对吗?

  3. 这两个陈述是否相等?

    let promise = new Promise(function(resolve, reject){
        resolve();
    });
    
    let promise = Promise.resolve();
    
  4. 为什么输出顺序如下?

    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 对象之前调用。有人可以告诉我为什么我错了。

标签: javascriptpromise

解决方案


第一个函数创建一个 Promise 对象,但在我们调用该函数之前不会调用它。相比之下,第二条语句创建了一个 Promise 对象并立即调用它。我对吗?

第一个函数只是一个函数。只要不调用它,就不会创建任何承诺。一旦调用它,与您作为替代的直接执行没有太大区别。

我认为当我们调用超时函数时,它首先会创建一个异步函数 setTimeout

setTimeout未创建,该函数作为内置函数存在。你可以说它叫做.

...并将其推到事件队列的尾部。

推送到队列的不是函数setTimeout本身。它是回调(resolve在这种情况下),包括参数、超时时间和放在活动计时器列表中的唯一计时器 ID。

在所有同步事件完成后,它将被调用并创建一个 Promise 对象。

Promise 对象是在timeout函数调用期间创建的。

传递给的回调new Promise通常称为承诺构造函数回调,在执行的那一刻,new Promise回调也被执行(同步)并创建承诺。

当同步代码完成时,即调用堆栈为空时,微任务队列首先被消耗。在这个阶段那里什么都没有,因此验证了任务队列。如果到那个时候一个活动的计时器已经到期,那么任务队列中就会有一个条目。

Promise 对象也会被推送到事件队列的尾部,在其他同步事件之后

Promise 对象没有放在任何队列中。当计时器到期时,排队的事件将作为一个新任务被触发,即resolve函数将被调用,这反过来将解决承诺,这反过来会将条目放入微任务队列中,每个then回调一个条目,并且await-相关的影响。在从任务队列中提取任何其他任务之前,微任务队列将在同一个(宏)任务中处理。

所以似乎有两个事件循环,对吗?

至少两个;可以有更多。例如,在浏览器上下文中,可能还有另一个队列用于处理有关 DOM 元素突变的事件。另请参阅此答案,了解一些不同的规范对此有何评论。

  1. 这两个陈述是否相等?

几乎是的;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产生输出
  • 调用栈为空
  • 微任务队列中没有更多条目

任务队列中没有更多条目。系统继续检查任务队列中的新条目。


推荐阅读