承诺是异步还是同步解决的?

3

最近我一直在使用JavaScript Promise,遇到了以下情况,让我开始思考:

var combinedArray = [];

function getArrayOne() {
    $http.post(arrayOnePath).then(function(arr) {
        combinedArray = combinedArray.concat(arr);
    }) // More code preventing me from using Promise.all(...)
}

function getArrayTwo() {
    $http.post(arrayTwoPath).then(function(arr) {
        combinedArray = combinedArray.concat(arr);
    }) // More code preventing me from using Promise.all(...)    
}

function getAllArrays() {
    getArrayOne();
    getArrayTwo();
}

当我编写这个逻辑时,我意识到如果两个Promise同时解决(因为它们访问共享资源),可能会存在潜在的竞争条件。经过更长时间的思考后,我意识到then(..)解析是在post返回后执行的,这意味着该代码正在JavaScript的同步执行环境中运行。
请问是否有人能够为我提供一些清晰的解释,说明两个combinedArray.concat(arr);语句是否会在两个Promise同时解决时出现问题?
[编辑] 根据一些评论,我想要补充说明的是,我不介意将数组按任意顺序连接到combinedArray中。

1
只要您的逻辑不依赖于发帖请求解析的顺序,那么就没问题。仅当例如combinedArray[0]应始终是getArrayOne的第一条记录时,才会遇到麻烦。 - Shilly
2
如果你期望你的combinedArray有一个预定的顺序,那么就会出现竞争条件。但是,两个JavaScript回调函数永远不会同时被调用。 - Bergi
尽管“单线程”JavaScript的答案是您要寻找的,但对问题标题的不同解读类似于此SO问题(“为什么JavaScript Promise then处理程序在其他代码之后运行?”),询问如何保证异步调用then处理程序。 - Jeff Bowman
哦,正如你所说,“更多的代码”并不妨碍你使用Promise.all()。实际上,你的具体代码示例需要使用类似Promise.all()的东西,否则你就无法知道两个异步操作何时完成,以及combinedArray实际上包含了结果。如果你展示给我们更多的代码,我们可以向你展示如何使用Promise.all() - jfriend00
3个回答

2

JavaScript是单线程的,即使在运行异步调用时也可以防止竞争条件。

有些情况下,JS会在后台使用另一个线程,比如node的I/O函数,而Web Worker API允许您生成一个独立但分离的线程(没有内存访问但它们可以传递消息)。

因为JS最初是单线程的,而运行时中的所有内容都依赖于此(并且旧代码假定它),所以他们不能只添加多线程和潜在的竞争条件。这将打破一切。因此,此代码将始终正确且安全地工作,因为承诺将添加到单个队列中,并一个接一个地解析。

即使在Web Worker(和节点等效)中,每个“线程”都有一个隔离的内存空间,不能直接访问另一个线程的变量。Web Worker特别使用postMessage方法以安全的方式序列化对象并将其发送到另一个线程。


2
@NicholasRobinson 不 - Omri Aharon
@NicholasRobinson,你需要学习事件循环 - zzzzBov
你可以使用 Promise.all()。请参阅:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all - Sander Sluis
2
那个“但是”对我来说没有意义。异步和线程之间没有太多关系。 - Bergi
1
@NicholasRobinson 对,我不得不读两遍你的片段才看到评论。抱歉。但我真的很好奇为什么你不能使用它。 - Sander Sluis
显示剩余8条评论

1

关于传递给 Promise API 的函数需要知道的事情:

  • 传递给Promise执行器(new Promise(fn))的函数fn会立即执行。

  • 传递给处理程序(.then(fn))的函数fn会异步执行。

在JavaScript环境中,除非使用Web Workers(感谢@zzzzBov),否则不会同时执行两个函数。无论如何,这都不是异步性意味着或暗示的。

在您的示例中没有竞争条件,因为竞争条件决定了实现中的问题。因此,尽管您无法预测哪个函数将先执行,但两种结果都不会对程序的操作产生不利影响。当然,除非您的程序依赖于其中一个连接操作先执行...(从您的编辑中可以看出它不依赖于此)。


"竞态条件" 维基百科

竞态条件或竞争风险是指电子、软件或其他系统的行为,其输出取决于其他不可控事件的顺序或时间。当事件的发生顺序与程序员预期的顺序不同步时,它就会成为一个错误。


1
“在 JavaScript 环境中,任何两个函数都不会同时执行。” 你知道 Web Workers 提供了多线程能力吗? - zzzzBov
@zzzzBov在我的答案中添加了这个限制条件。谢谢。 :-) - sdgluck
2
@zzzzBov:可以说每个工作线程都在自己的环境中运行。 - Bergi
@Bergi,我差点就自己提出了这个论点,但我认为值得保留最初的评论,只是关于JS具有多线程的事实。 - zzzzBov

0

正如我认为已经解释过的那样,您的用户级JavaScript是单线程的(除了这里不涉及的webWorkers)。

您的JavaScript中的任何两个部分都不会在完全相同的时刻运行,因此您没有竞争条件。您特定的代码示例确实存在一个问题,即无法知道何时完成了您的两个异步操作,以便您可以使用结果。因此,您可以使用以下结构:

function getArrayOne() {
    return $http.post(arrayOnePath).then(function(arr) {
        // do any processing here on or with arr
        return arr;
    })
}

function getArrayTwo() {
    return $http.post(arrayTwoPath).then(function(arr) {
        // do any processing here on or with arr
        return arr;
    })
}

function getAllArrays() {
    return Promise.all(getArrayOne(), getArrayTwo()).then(function(results) {
        // results[0] is first array
        // results[1] is second array
        return results[0].concat(results[1]);
    });
}

getAllArrays().then(function(results) {
     // all results available
}, function(err) {
     // error occurred here in either async operation
});

这不仅告诉您何时完成所有异步操作并为您提供组合结果,而且它们是有序的,并且可以从任一操作中传播错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接