0

Is foreach (Array.prototype.forEach) guaranted to loop in the same order every time over the same data set? And why?

Xsmael
  • 3,624
  • 7
  • 44
  • 60
  • What loop and what object? – SLaks Nov 29 '18 at 19:53
  • Because `forEach` is not asynchronous? It takes next argument only if the previous iteration has finished. And of course if you are not calling any asynchro function in that loop. – kind user Nov 29 '18 at 19:53
  • 1
    Do you mean `Array.prototype.forEach`? And what do you mean by *"why"*? – jonrsharpe Nov 29 '18 at 19:53
  • 1
    Sure, but if the function supplied to it does some asynchronous processing with the value, there is no guarantee that that processing happens in any particular order. `[1, 2, 3, 4, 5].forEach((n) => setTimeout(() => console.log(n), Math.random() * 1000))` – Scott Sauyet Nov 29 '18 at 19:57
  • @jonrsharpe I mean I wanted to understand what's behind the order logic in either case. – Xsmael Nov 29 '18 at 20:19

2 Answers2

1

If the implementation is sticking to the standard then yes, forEach processes the array in order except for sparse arrays where uninitialized values are ignored. forEach() is described in the spec:

forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.

It loops through the index starting with:

Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length" Let k be 0.
Repeat, while k < len
...
Increase k by 1

In javascript Arrays are objects and have a length property and integer keys which are the indexes. The spec is saying it will start at k=0 and call the arr[k] in increasing order for k < length. It will call the callback for all values where arr has a property k

An example of a sparse array where forEach ignores elements might look like this:

let a  = Array(10)
a[5] = "Five"
a[9] = "Nine"
console.log(a)
// ignores initialized values:
a.forEach((item) => console.log(item))

If you are doing asynchronous things inside the callback, it can appear that the callback is not happening in order, but it still is.

EDIT based on comment:

You can with a little hacking pass an object to forEach that looks like an array (it has a length and integer keys). forEach will call the items in index order, not the order they are defined on the object. For example:

// an object that has enough info
// for forEach to use
let o = {
    3: "three",
    2: "two",
    0: "zero",
    1: "one",

    length: 4
}

// calls in order of indexes, not order keys were defined
Array.prototype.forEach.call( o, (i) => console.log(i))
Mark
  • 90,562
  • 7
  • 108
  • 148
  • does it really loop "in ascending order" or according to the order in wich each elements of the object has been define – Xsmael Nov 29 '18 at 20:23
  • 1
    @Xsmael see the edit for an edge case example. `forEach` calls in ascending order of key which in the case of arrays is the index. – Mark Nov 29 '18 at 20:26
1

Yes, forEach will wait for the previous item to complete before processing the next one

//Sync methods;
[1,2,3].forEach(function(e){
    console.log(e)
});
//Output:
//1
//2
//3

If you're using Async methods inside the loop, they will be called in order but the loop will not wait for the callback.

//Async Methods
[1,2,3].forEach(function(e){
    someAsyncMethod(e, console.log);
});
//Output example:
//3
//1
//2

This answer have some methods to use async functions inside foreach.

As for why, there is an international standard called ECMAScript all JavaScript implementations should adhere to it; the behaviour of Array.prototype.forEach() is specified in this standard.

You cand find the specification and the pseudo-code for Array.prototype.forEach() at ECMAScript® 2018 Language Specification

Cheloide
  • 793
  • 6
  • 21