返回新的 Promise 和 Promise.resolve 的区别

Bei*_*man 8 javascript node.js async-await es6-promise

对于下面的代码片段,我想了解 NodeJS 运行时如何处理事情:

const billion = 1000000000;

function longRunningTask(){
    let i = 0;
    while (i <= billion) i++;

    console.log(`Billion loops done.`);
}

function longRunningTaskProm(){
    return new Promise((resolve, reject) => {
        let i = 0;
        while (i <= billion) i++;

        resolve(`Billion loops done : with promise.`);
    });
}

function longRunningTaskPromResolve(){
    return Promise.resolve().then(v => {
        let i = 0;
        while (i <= billion) i++;

        return `Billion loops done : with promise.resolve`;
    })
}


console.log(`*** STARTING ***`);

console.log(`1> Long Running Task`);
longRunningTask();

console.log(`2> Long Running Task that returns promise`);
longRunningTaskProm().then(console.log);

console.log(`3> Long Running Task that returns promise.resolve`);
longRunningTaskPromResolve().then(console.log);

console.log(`*** COMPLETED ***`);
Run Code Online (Sandbox Code Playgroud)

第一种方法:

longRunningTask()函数将按预期阻塞主线程。

第二种方法:

longRunningTaskProm()中将相同的代码包装在 Promise 中,期望执行将远离主线程并作为微任务运行。似乎不是这样,想了解幕后发生的事情。

第三种方法:

第三种方法longRunningTaskPromResolve()有效。

这是我的理解:

Promise 的创建和执行仍然与主线程挂钩。只有 Promise 解析的执行作为微任务移动。

我有点不相信我发现的任何资源和我的理解。

jfr*_*d00 6

所有这三个选项都在主线程中运行代码并阻止事件循环。它们开始运行while循环代码的时间和它们阻塞事件循环的时间略有不同,这将导致它们与某些控制台消息的运行时间不同。

第一个和第二个选项立即阻止事件循环。

第三个选项阻止在下一个滴答开始的事件循环 - 即Promise.resolve().then()调用您传递给的回调.then()(在下一个滴答上)。


第一个选项只是纯同步代码。毫不奇怪,它会立即阻塞事件循环,直到while循环完成。

在第二个选项中,新的 Promise 执行器回调函数也被同步调用,因此它再次立即阻塞事件循环,直到while循环完成。

在第三个选项中,它调用:

Promise.resolve().then(yourCallback);
Run Code Online (Sandbox Code Playgroud)

Promise.resolve()创建一个早已解决的承诺,然后调用.then(yourCallback)上的新承诺。这计划yourCallback在事件循环的下一个滴答上运行。根据承诺规范,.then()处理程序总是在事件循环的未来滴答上运行,即使承诺已经解决。

同时,在此之后的任何其他 Javascript 都会继续运行,并且只有当该 Javascript 完成后,解释器才会进入事件循环的下一个滴答并运行yourCallback。但是,当它确实运行该回调时,它会在主线程中运行,因此会阻塞直到它完成。

Promise 的创建和执行仍然与主线程挂钩。只有 Promise 解析的执行作为微任务移动。

示例中的所有代码都在主线程中运行。一个.then()处理器定于事件循环(在主线程仍然)的未来蜱运行。这种调度使用了一个微任务队列,它允许它在事件队列中的其他一些东西之前得到它,但它仍然在主线程中运行并且它仍然在事件循环的未来滴答上运行。

此外,“履行承诺”这个词有点用词不当。Promise 是一个通知系统,您可以安排在将来的某个时间使用.then().catch().finally()在Promise 上使用它们运行回调。因此,一般而言,您不想考虑“执行承诺”。您的代码执行导致创建一个承诺,然后您注册该承诺的回调以根据该承诺发生的情况在未来运行。Promises 是一个专门的事件通知系统。


Promise 有助于在事情完成时通知您或帮助您安排事情何时进行。他们不会将任务移动到另一个线程。


例如,您可以setTimeout(fn, 1)在第三个选项之后插入一个right 并看到超时被阻止运行,直到第三个选项完成。这是一个例子。而且,我已将阻塞循环全部设为 1000 毫秒长,以便您可以更轻松地看到。在这里在浏览器中运行它或复制到 node.js 文件并在那里运行它以查看 .jssetTimeout()的执行时间是如何阻止按时执行的longRunningTaskPromResolve()。所以,longRunningTaskPromResolve()还是阻塞。.then()当它开始运行时,将它放在处理程序中会发生变化,但它仍然是阻塞的。

Promise.resolve().then(yourCallback);
Run Code Online (Sandbox Code Playgroud)

如果您运行此代码段并查看每条消息的确切输出时间以及与每条消息关联的时间,您就可以看到事情开始运行的确切顺序。

这是我在 node.js 中运行它时的输出(添加行号以帮助解释下面的解释):

1    0.000: *** STARTING ***
2    0.005: longRunningTask() starting
3    1.006: ** longRunningTask() done **
4    1.006: longRunningTaskProm() starting
5    2.007: About to call resolve() in longRunningTaskProm()
6    2.007: longRunningTaskPromResolve() starting
7    2.008: Scheduling 1ms setTimeout
8    2.009: *** First sequence of code completed, returning to event loop ***
9    2.010: ** longRunningTaskProm().then(handler) called **
10   2.010: Start running .then() handler in longRunningTaskPromResolve()
11   3.010: About to return from .then() in longRunningTaskPromResolve()
12   3.010: ** longRunningTaskPromResolve().then(handler) called **
13   3.012: 1ms setTimeout Got to Run
Run Code Online (Sandbox Code Playgroud)

这是一个分步注释:

  1. 事情开始。
  2. longRunningTask() 发起。
  3. longRunningTask()完成。它是完全同步的。
  4. longRunningTaskProm() 发起。
  5. longRunningTaskProm()调用resolve()。从中可以看出,promise executor 函数(传递给 new Promise(fn)` 的回调也是完全同步的。
  6. longRunningTaskPromResolve()发起。您可以看到longRunningTaskProm().then(handler)尚未调用处理程序 from 。它已被安排在事件循环的下一个滴答上运行,但由于我们还没有回到事件循环,因此还没有被调用。
  7. 我们现在正在设置 1ms 计时器。请注意,此计时器仅在我们启动后 1 毫秒设置longRunningTaskPromResolve()。那是因为还longRunningTaskPromResolve()没有做太多。它运行了Promise.resolve().then(handler),但所做的只是安排handler在事件循环的未来滴答上运行。所以,只用了 1 毫秒来安排它。该函数的长时间运行部分尚未开始运行。
  8. 我们到达这一系列代码的末尾并返回到事件循环。
  9. 计划在事件循环中运行的下一件事情是处理程序,longRunningTaskProm().then(handler)以便调用它。您可以看到它已经在等待运行,因为它在我们返回到事件循环后仅运行了 1 毫秒。该处理程序运行,我们返回到事件循环。
  10. 计划在事件循环中运行的下一个事件是来自的处理程序,Promise.resolve().then(handler)因此我们现在看到它开始运行,并且由于它已经排队,因此在前一个事件完成后立即运行。
  11. longRunningTaskPromResolve()运行循环正好需要 1000 毫秒,然后它从它的.then()处理程序返回,该处理程序调度.then()该承诺链中的下一个处理程序在 eventl 循环的下一个滴答上运行。
  12. 这样.then()就可以运行了。
  13. 然后,最后当没有.then()安排运行的处理程序时,setTimeout()回调开始运行。它被设置为在 1 毫秒内运行,但它被所有以更高优先级运行的承诺操作延迟了,所以它没有运行 1 毫秒,而是在 1004 毫秒内运行。