【问题标题】:How Native JavaScript Promise Handles Blocking Code原生 JavaScript Promise 如何处理阻塞代码
【发布时间】:2015-03-05 00:55:38
【问题描述】:

(发布此内容时的第一个障碍是决定一个好的标题 - 希望我做得不错。)

我对原生 JavaScript Promise 对象的行为方式(在 Windows 7 的 Chrome 和 Firefox 中测试)以及它们是否实际并行执行感到有些困惑。我在这里寻求关于 SO 的启发,但目前还没有发现。

考虑一下这段代码:

(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?');
})();

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

这是什么时候?
答:499999999
b: 499999999

看起来虽然创建 Promise 本身不是阻塞操作,但第一个中的阻塞循环有效地阻碍了第二个第一个解析。

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

也许这一切都是清醒和花花公子,但我不太明白这一切的意义。 Promise 对象不应该并行执行和解析吗?

我非常有义务澄清这种情况,以及如何正确使用 Promise 进行并行执行。

(请注意,这个问题与 Promise.resolve()Promise.all() 等无关,而是关于 JavaScript Promise 的并行或非并行性质。)


编辑:在我的一些 cmets 中,我说我遇到了与上述相同的问题,即使用异步替换循环也是如此。这是错误的,为了避免任何歧义,这里有一个例子:
<!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() 代码以使它们“慢”。虽然可能不是 100% 绝对防弹,但它应该为在这种情况下的测试提供足够的解决方案,并且控制台的输出如下:

已解决的页面/Page1.html。
已解决的页面/Page2.html。
第一个承诺已解决。
已解决的页面/SlowPage.aspx.
已解决的页面/SlowerPage.aspx.
所有的承诺都解决了。

在我最初的问题中,我认为“这是什么时候?”这句话令人困惑。在任何Promise 对象解析之前记录。同样,我认为在Promise.race 意识到至少一个Promise 已解决之前,html 页面始终(总是?)解决有点出乎意料。如果有人愿意进一步详细说明,我很想听听,但如果没有,我对“就是这样”的结论暂时感到满意。

编辑:我真正的意思是“并发”而不是“并行”。

【问题讨论】:

  • JavaScript 是按顺序同步执行的。您的代码中没有异步操作。我不知道你为什么会期待不同的结果。
  • JavaScript 是单线程的(除非您使用网络工作者等)。您发布的代码将按照请求的顺序运行,因为您的 Promise 中没有异步工作,它们将按调用的顺序登录。
  • 如果我发出异步 AJAX 请求而不是循环,但问题仍然存在。
  • @OskarLindberg 您是在完成 AJAX 请求时调用resolve(),还是在您触发它之后调用 - 即您是否将调用 resolve 移动到成功回调或类似的,或者AJAX 请求是否直接替换了您问题中的 for 循环,而对 resolve 的调用仍在其后?
  • 我在完成时解决了。我不应该吗?着手处理响应并解决修改后的结果似乎是个好主意。

标签: javascript concurrency promise


【解决方案1】:

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

当你实例化你的第一个 Promise 时,底层实现是这样的,在创建之后,JavaScript 将控制权交还给下一条指令,即开始循环。这条指令(如果您愿意,可以使用 Promise 中的 function IETF)开始运行,并且不会停止,直到循环完成全部过程。事件循环在任何时候都没有办法注意到您的循环已“部分完成”,但在下一次迭代之前可以继续执行几个操作。

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

如果您想在不调用网络工作者或切换语言的情况下以另一种方式执行此操作,但会付出巨大的性能代价,您可以process.nextTick()(或者,因为您在浏览器中,setTimeout(function() {}, 0))每次迭代您的循环以查看我所说的是否正确。然后,您将看到 promise #2 正在完成,因为循环的每次迭代都被“交回”给事件循环。

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

【讨论】:

  • 是的 JavaScript 是单线程的(除非 setTimeout 和所有这些),但我不明白例如Promise.race() 如果我仍然需要等待一个“重”Promise 完成,然后才能开始处理第二个“更轻”的工作。那不是按照声明的顺序序列化执行吗..?
  • @OskarLindberg "除非 setTimeout" - 使用超时仍然是单线程的。如果您将超时设置为 2 秒,另一个设置为 3 秒,如果第一个需要 5 秒才能运行,则第二个将在 7 秒后才会发生,而不是 3。javascript 中没有并行执行,永远(除了网络工作者)。 Promise.race 用于运行异步代码时——例如对服务器的 ajax 调用,第一个返回的将“获胜”——你无法绕过 javascript 中的阻塞代码——它总是会阻塞一切,包括 UI(除非网络工作者)。
  • @OskarLindberg:首先,setTimeout 在单线程方面没有做任何事情。它只是将执行控制权交还给其他东西。 Promise.race 也不这样做。您的问题是“运行繁重的、连续的并且在任何时候都没有给予控制权”,不是这样做的方法,Promises 在这方面没有帮助。 Promise 是为纯粹的异步、非阻塞任务而设计的(XHR 调用是该类别中最好和最聪明的)。
  • 是的,我知道。我的陈述很粗心——感谢您的纠正。我只是指这样一个事实,即您可以使用 setTimeoutsetInterval 之类的东西获得“类似行为”,就像 Renauld 先生在上面的回答中所建议的那样。编辑:实际上,雷诺先生并没有提出任何这样的建议——我又粗心了,我道歉。
  • 关于我粗心的最后一点 - 我真的应该一开始就谈论“并发”而不是“并行”。
猜你喜欢
  • 2019-11-16
  • 1970-01-01
  • 2014-12-24
  • 2014-09-08
  • 2015-10-06
  • 1970-01-01
  • 2020-02-04
相关资源
最近更新 更多