对承诺感到困惑

时间:2019-06-28 02:35:09

标签: javascript promise

resolve的实际作用是什么?
考虑下面的代码。 打印:1 3 4 5 6 9 7 10 11 2.
不管resolve写在什么地方,它都打印相同的内容!
有人可以解释为什么会这样吗?

    new Promise((resolve, reject) => {
        console.log(1);
        setTimeout(() => {
            console.log(2);
        }, 0);
        resolve();
        new Promise((resolve, reject) => {
            console.log(3);
            resolve();
        })
        .then(() => {
            console.log(4);
        })
        .then(() => {
            console.log(9);
        })
        .then(() => {
            console.log(10);
        })
        .then(() => {
            console.log(11);
        })
        ;
        // resolve()
    }).then(() => {
        console.log(5);
        new Promise((resolve, reject) => {
            console.log(6);
            resolve();
        }).then(() => {
            console.log(7);
        });
    })

2 个答案:

答案 0 :(得分:3)

  

“解决”实际上是做什么的?

呼叫resolve(x)有三件事。

  1. 它更改了兑现承诺的内部状态。一旦状态更改为实现,就无法再次更改状态。这是一种单向的,永久的改变。

  2. 它将值x(无论传递给解析的单个参数)设置为promise的已解析值(此值存储在promise内部)。如果没有任何内容传递给resolve(),则解析的值为undefined

  3. 它将事件插入到事件队列中,以触发当前承诺的.then()处理程序在事件循环中即将到来的周期中被调用。这样可以安排.then()处理程序在当前Javascript执行线程完成后运行。

现在,解释一下您在控制台中看到的顺序。首先,一些有助于理解这一点的东西。

  • promise执行程序函数(传递给new Promise(fn)的回调)被同步调用(在当前执行线程中间)。
  • setTimeout()计时器(在JS引擎内部)触发时,计时器回调将插入事件队列中,并将在以后的事件循环中获取。
  • 当一个诺言解决时,一个事件将插入事件队列,并将在以后的事件循环中获取。
  • 事件循环中有多种类型的事件队列,并非所有优先级都相同。通常,promise事件将在大多数其他类型的事件之前被拾取,尽管这可能因实现方式而有所不同。因此,当将多种类型的事件都放入事件队列中以便它们同时在其中时,这可以影响首先被调用的事件。
  • 添加到事件队列中的多个.then().catch()处理程序的处理顺序是:它们最初以FIFO为基础被触发(彼此相对)(先入先出)
  • 当使用诸如fn().then(f1).then(f2).then(f3)之类的诺言链时,请记住,每个.then()都会返回一个新的诺言,该诺言将有自己的时间被解决或拒绝,取决于它之前或之后的时间,具体取决于什么发生在其处理函数中。

因此,这是代码中的事件顺序:

  1. 第一个promise执行器函数被调用,因此您得到输出1
  2. 创建的计时器的超时时间为0。很快,某个计时器回调事件将添加到事件队列中。
  3. 您在第一个诺言上致电resolve()。这会将事件/任务插入promise队列,以在事件循环的未来周期调用其.then()处理程序。此序列Javascript代码的其余部分将继续执行。但是,请注意,由于其链式.then()方法尚未执行,因此第一个承诺还没有任何.then()处理程序。
  4. 您创建了第二个Promise,它的执行程序函数立即被调用,它输出3
  5. 您根据第二个承诺致电resolve()。这会将事件/任务插入promise队列,以在事件循环的未来周期调用其.then()处理程序。此序列Javascript代码的其余部分将继续执行。
  6. 在第二个承诺中调用
  7. .then()。这会在第二个诺言中注册一个.then()处理程序回调函数(将其添加到内部列表中)并返回一个新的诺言。
  8. 在新返回的诺言(第三诺言)上调用
  9. .then()。这会在第三个承诺中注册一个.then()处理程序回调函数(将其添加到内部列表中)并返回一个新的承诺。
  10. 在新返回的诺言(第四诺言)上调用
  11. .then()。这会在第四个承诺中注册一个.then()处理程序回调函数(将其添加到内部列表中)并返回一个新的承诺。
  12. 在新返回的诺言(第五诺言)上调用
  13. .then()。这会在第五个诺言中注册一个.then()处理程序回调函数(将其添加到内部列表中)并返回一个新的诺言。
  14. 第一个诺言的执行程序功能最终返回。
  15. .then()在第一个承诺中被调用。这会在第一个诺言中注册一个.then()处理程序回调函数(将其添加到内部列表中)并返回一个新的诺言。
  16. 由于第二个诺言中的.then()处理程序在第一个诺言中的.then()处理程序之前运行,因此它将首先放入任务队列中,因此接下来将得到输出4
  17. .then()处理程序运行时,它将解析它先前创建的承诺,第三个承诺,并将一个任务添加到承诺队列中以运行其.then()处理程序。
  18. 现在,任务队列中的下一个项目是第一个诺言中的.then()处理程序,因此它有机会运行,您会看到输出5
  19. 然后,它使用new Promise(...)创建另一个新的Promise,并运行其executor函数。这将导致显示输出6
  20. 这个新的承诺已由resolve()解决。
  21. 调用它的.then(),它注册一个.then()回调并返回新的Promise。
  22. 当前的Javascript序列已完成,因此可以返回到下一个事件的事件循环。预定的下一个事件是第四个承诺的.then()处理程序,因此它被从事件队列中拉出,您将看到输出9
  23. 运行此.then()处理程序可以解决第五个诺言,并将其.then()处理程序插入到诺言任务队列中。
  24. 返回到事件队列以获取下一个Promise事件。在代码中,我们从最后的.then()获得了new Promise().then()处理程序,并得到了输出7
  25. 重复上述过程,您将看到输出11,然后是12
  26. 最后,promise任务队列为空,因此事件循环查找优先级不高的其他类型的事件,并找到setTimeout()事件并调用其回调,最后得到输出{{ 1}}。

因此,2由于一些原因而排在最后。

  1. Promise事件在计时器事件之前运行(在ES6中),因此所有排队的promise事件都在任何排队的计时器事件之前提供。
  2. 因为您所有的承诺都可以解决,而实际上不必等待任何其他异步事件完成(这不是现实世界的行为,通常也不是一个人使用承诺的方式或原因),所以计时器必须等到它们完成在机会运行之前,一切都已完成。

还有其他一些评论:

  1. 有时可以找出不同且独立的promise链中各种setTimeout()处理程序的相对触发顺序(这仅在这里是可能的,因为没有真正的异步promise解析时间不确定),但是如果您确实需要特定的执行顺序,那么最好将您的操作链接在一起,以明确指定您希望事情在代码中运行的顺序。这消除了对本地Javascript引擎的详细实现细节的任何依赖,并使代码更具解释性。换句话说,阅读您的代码的人不必执行我列出的22个步骤即可遵循所需的执行顺序。取而代之的是,代码将仅通过直接的Promise链接指定顺序。

  2. 在真实代码中,在.then()处理程序中创建孤立的,断开连接的Promise链是不寻常的。因为您没有从.then()处理程序中返回那些承诺,而是将它们插入到父承诺链中,所以无法从那些断开的承诺链中传达结果或错误。尽管偶尔有理由编写无需与外界进行通信的即发即弃操作,但这是不寻常的,通常是问题代码的标志,该代码无法正确传播错误,并且结果无法与其余所有操作正确同步。

  

当我将'resolve'放在后面时,它会打印出相同的内容!

正如您所发现的,这并没有真正改变任何东西。直到执行程序函数完成运行并返回之后,.then()之后的.then()才执行,因此您在执行程序中调用new Promise(...)的位置并不重要。换句话说,resolve()处理程序中的任何一个甚至都无法注册,直到promise执行者返回之后,因此无论您在promise执行者中调用.then()的位置如何,结果都是相同的。

答案 1 :(得分:2)

resolve表示asynchronous task的完成。

在下面的代码中,

new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0);
    resolve();
    new Promise((resolve, reject) => {
        console.log(3);
        resolve();
    })
    .then(() => {
        console.log(4);
    })

您已经创建了new Promise,并立即使用resolve()对其进行了解决,因此它不会等待setTimeout被执行。 resolve();之后紧跟着 new Promise,创建新的Promise,然后执行立即的then部分。

.then中,您没有退回任何东西,因此then的链接不正确。返回then中的值以正确链接它。

new Promise((resolve) => {
    console.log("1");
    setTimeout(() => {
        resolve(2);
    });
}).then((val) => {
    console.log(val);
    return "3";
}).then((val) => {
    console.log(val);
    return "4";
}).then((val) => {
    console.log(val);
});