Native JavaScript Promise 如何处理阻塞代码

How Native JavaScript Promise Handles Blocking Code

(发布这篇文章的第一个障碍是决定一个好的标题 - 希望我没问题。)

我对本机 JavaScript Promise object 的行为方式感到有些困惑(在 Chrome 中测试,Firefox 在 Windows 7 中测试),并且它们是否实际上并行执行。我在这里寻求启发,但到目前为止发现 none。

考虑这段代码:

(function PromiseTester() {
    var counter = 0;
    new Promise(function(resolve) {
        for(var i = 0; i < 500000000; i++)
            counter = i;

        resolve();
    }).then(function() {console.log('a: ' + counter)});

    new Promise(function(resolve) {
        resolve();
    }).then(function() {console.log('b: ' + counter)});

    console.log('When is this?');
})();

如何解释控制台的以下输出?

When is this?
a: 499999999
b: 499999999

虽然创建 Promises 本身并不是阻塞操作,但第一个中的阻塞循环有效地阻碍了第二个首先解析。

我还尝试将 Promiseobject 放入数组中并用 Promise.race() 对其进行测试。 race() Promisethen() 方法中的代码似乎在第一个 Promise 中的循环完成后才执行。

也许这一切都很清醒和花花公子,但我不太明白这一切是怎么回事。 Promise object 不应该并行执行和解析吗?

我非常感谢任何澄清情况的尝试,以及如何正确使用 Promise 进行并行执行。

(请注意,这个问题不是关于 Promise.resolve()Promise.all() 等,而是关于 JavaScript Promise 的平行或可能不平行的性质.)


编辑: 在我的一些评论中,我说我遇到了与上述相同的问题,即使用异步的东西替换循环也是如此。这是错误的,为了避免歧义,这里有一个例子:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>Promise Tester</title>
    </head>
    <body>
        <p>Check the console!</p>

        <script type="text/javascript">
            (function Main() {
                var urls = [
                    'Pages/SlowPage.aspx',
                    'Pages/Page1.html',
                    'Pages/SlowerPage.aspx',
                    'Pages/Page2.html'
                ];

                var promises = [];

                for (var i = 0; i < urls.length; i++) {

                    (function AddPromise(url) {
                        promises.push(
                            new Promise(function (resolve) {
                                var request = new XMLHttpRequest();
                                request.onload = function () {
                                    resolve(url);
                                };
                                request.open('GET', url, true);
                                request.send();
                            })
                            .then(function (result) {
                                console.log('Resolved ' + url + '.');
                            })
                        );
                    })(urls[i])
                }

                Promise
                    .race(promises)
                    .then(function () {
                        console.log('First promise resolved.');
                    });

                Promise
                    .all(promises)
                    .then(function () {
                        console.log('All promises resolved.');
                    });
            })();
        </script>
    </body>
</html>

html 页面就是这样 - 简单明了 HTML 页面,但在 aspx 页面中我放置了一些服务器端 Thread.Sleep() 代码来制作它们 "slow"。虽然可能不是one-hundred-percent-absolutely-bulletproof,但它应该提供足够的解决方案来测试这个上下文,控制台的输出如下:

Resolved Pages/Page1.html.
Resolved Pages/Page2.html.
First promise resolved.
Resolved Pages/SlowPage.aspx.
Resolved Pages/SlowerPage.aspx.
All promises resolved.

在我最初的问题中,我认为短语 "When is this?" 在 之前 任何 Promise object 被记录是令人困惑的解决。同样,我认为 html 页面始终(总是?)在 Promise.race 意识到至少一个 Promise 已解决之前解决,这有点出乎意料。如果有人愿意进一步详细说明,我很想听听,但如果不是,我现在对 "that's just how it is" 的结论感到满意。

编辑: 我的意思是 "concurrent" 而不是 "parallel" 以上所有内容。

啊,事件循环和多线程之间的混淆...

当你实例化你的第一个承诺时,底层实现是这样的,在创建之后,JavaScript 将控制权交还给下一条指令,即开始循环。此指令(如果您愿意,承诺中的 function IETF)开始 运行ning,并且 不会停止 直到循环具有 运行 它的完整课程。在任何时候,事件循环都无法注意到您的循环是 "partially done",但是可以在下一次迭代之前插入几个操作。

当循环结束时,promise 被标记为已完成,事件循环决定按顺序选择下一个 - 你的第二个 promise!

如果您想在不调用网络工作者或切换语言的情况下以另一种方式进行操作,但会牺牲巨大的性能,您可以process.nextTick()(或者,由于您在浏览器中,setTimeout(function() {}, 0)) 循环的每次迭代,以查看我所说的是正确的。然后你会看到 promise #2 正在完成,因为循环的每次迭代都是 "handed back" 到事件循环。

实际上,你期望 JS 是多线程的,它只是事件驱动的。观念差异影响巨大