3

I have been working with JavaScript promises recently and came across the following situation that got me thinking:

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

While I was writing this logic it dawned on me that there could be a potential race condition if both promises resolve at the same time (as they access a shared resource). After thinking about this for a little while longer I realized that the then(..) resolutions are executing after the post returns which means this code is running in JavaScript's synchronous execution environment.

Could someone please provide some clarity for me on whether the two combinedArray.concat(arr); statements could cause a problem if both promises resolve at the same time?

[Edit] Following some of the comments I just want to add that I don't mind what order the arrays are concatenated into combinedArray.

Nicholas Robinson
  • 1,359
  • 1
  • 9
  • 20
  • 1
    As long as your logic doesn't depend on the order the post requests resolve, you're fine. You'll only get into trouble if for example, combinedArray[0] should always be the first record from getArrayOne. – Shilly Jul 06 '16 at 14:51
  • 2
    There is a race condition if you expect your `combinedArray` to have a predetermined order. But no, two javascript callbacks are never called at the same time. – Bergi Jul 06 '16 at 14:58
  • Though the "single threaded" Javascript answer is the one you were looking for, a different reading of the question title resembles [this SO question](http://stackoverflow.com/q/38059284/1426891) ("Why does JavaScript Promise then handler run after other code?") asking how `then` handlers are guaranteed to be invoked asynchronously. – Jeff Bowman Jul 06 '16 at 19:48
  • Uhhh, "more code" as you indicate does not prevent you from using `Promise.all()`. And, in fact, your specific code example needs to use something like `Promise.all()` because otherwise, you have no way of knowing when the two async operations are done and `combinedArray` actually contains the results. If you show us what the more code actually is, we can show you how to use `Promise.all()` with it. – jfriend00 Jul 07 '16 at 00:54

3 Answers3

3

JavaScript is single-threaded, preventing race conditions even when running asynchronous calls.

There are cases where JS will use another thread in the background, like node's I/O functions, and the web worker API allows you to spawn an isolated but seperate thread (no memory access but they can pass messages).

Because JS was originally single-threaded and everything in the runtime depends on that (and old code assumes it), they can't just add multi-threading and the potential race conditions. It would break everything. So this code will always work correctly and safely, as the promises will be added to a single queue and resolve one after another.

Even in web workers (and the node equivalent), each "thread" has an isolated memory space and cannot directly access variables from another thread. Web workers specifically use a postMessage method to serialize objects and send them to another thread in a safe manner.

ssube
  • 47,010
  • 7
  • 103
  • 140
  • So I might run into issues with `combinedArray` if both resolve at the same time? [Answered in edit] – Nicholas Robinson Jul 06 '16 at 14:48
  • 2
    @NicholasRobinson No – Omri Aharon Jul 06 '16 at 14:49
  • @NicholasRobinson, [you're going to want to learn about the event loop](http://stackoverflow.com/q/21607692/497418). – zzzzBov Jul 06 '16 at 14:52
  • @zzzzBov Thanks, I will check that out. – Nicholas Robinson Jul 06 '16 at 14:54
  • You can use Promise.all(). See: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all – Sander Sluis Jul 06 '16 at 14:55
  • @aximus I am aware of that, there are reasons that I can't make use of `Promse.all(..)` unfortunately. I did mention that in the code snipit. – Nicholas Robinson Jul 06 '16 at 14:58
  • 2
    That "but" makes no sense to me. Asynchrony and threading don't have much to do with each other. – Bergi Jul 06 '16 at 14:59
  • 1
    @NicholasRobinson right, I had to read your snippet twice to see the comment. Sorry. I'm really curious why you can't use it though. – Sander Sluis Jul 06 '16 at 15:02
  • @Bergi the title asks if promises are sync or async, but the body asks about race condition. Promises are async but still unable to race, because of the rest of the answer. – ssube Jul 06 '16 at 15:07
  • @aximus No worries. The reason basically boils down to performance and the way that the code proceeds from here. When one of promises resolve I can start working with `combinedArray` before the other promise is resolved (So there is no reason to wait for the other). It is also not a fatal issue if some of the promises is rejected, so by not using `Promise.all(..)` I can have one resolve and the other reject. – Nicholas Robinson Jul 06 '16 at 15:10
  • @NicholasRobinson still I think it's better to use Promise.all. Give the same callback to $http.post(arrayOnePath), $http.post(arrayTwoPath), and Promise.all(), and use that one callback function to handle the data. But this boils down to preferences. – Sander Sluis Jul 06 '16 at 15:14
  • @aximus `Promise.all` has very different behavior here, since it requires *all* promises to resolve successfully. Some of the other methods, like `map` or `each`, would keep the same behavior but allow the nice promise-array syntax. – ssube Jul 06 '16 at 15:16
  • @aximus I do agree that that `Promise.all(..)` is better and cleaner, but a lot of though has gone into this and that is the only repeating code. The reason that we went with this solution is mainly because we don't want to be waiting around while one promise times out (for example) and we have usable data. – Nicholas Robinson Jul 06 '16 at 15:17
1

Things to know about functions passed into the Promise API:

  • A function fn that is passed to a Promise executor (new Promise(fn)) is executed immediately.

  • A function fn that is passed to a handler (.then(fn)) is executed asynchronously.

No two functions will ever be executing at the same time in a JavaScript environment, unless you are using Web Workers (thanks @zzzzBov). Either way, this is not what asynchrony means or implies.

There is no race condition in your example as a race condition dictates a problem with the implementation. So although you cannot predict which of the functions will execute before the other, neither outcome adversely affects the operation of the program. Unless of course, your program depends on one of the concatenation operations being performed first... (which I can see that it doesn't, from your edit).


"Race condition" Wikipedia:

A race condition or race hazard is the behavior of an electronic, software or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended.

sdgluck
  • 24,894
  • 8
  • 75
  • 90
  • 1
    "No two functions will ever be executing at the same time in a JavaScript environment." are you aware that Web Workers provide multi-threading capabilities? – zzzzBov Jul 06 '16 at 15:12
  • @zzzzBov Added that caveat to my answer. Thank you. :-) – sdgluck Jul 06 '16 at 15:26
  • 2
    @zzzzBov: One could argue that each worker runs in its own environment. – Bergi Jul 06 '16 at 15:38
  • @Bergi, I nearly made that argument myself, but I thought it was worth keeping the initial comment just about the fact that JS has multi-threading. – zzzzBov Jul 06 '16 at 15:53
0

As I think has already been explained, your user-level Javascript is single threaded (except for webWorkers which are not involved here).

No two pieces of your Javascript are ever running at the same exact moment so you have no race condition. Your specific code example does have a problem knowing when both of your async operations are done so that you can use the results. As such, you can use a construct like this:

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
});

Not only does this tell you when all the async operations are done and give you the combined results, but they are in order and it propagates errors from either operation.

jfriend00
  • 683,504
  • 96
  • 985
  • 979