ReactHustle

Ultimate Guide to forEach() in JavaScript

Jasser Mark Arioste

Jasser Mark Arioste

Ultimate Guide to forEach() in JavaScript

I would like to share my knowledge about forEach function. I've seen a lot of guides out there that are incomplete so I'll try my best to cover everything in this tutorial. 

forEach() Definition #

First let's start with forEach definition. Below is the forEach documentation based the typescript es5 definition: lib.es5.d.ts 

interface Array<T> {
    ...
    /**
     * Performs the specified action for each element in an array.
     * @param callbackfn  A function that accepts up to three arguments. 
     * forEach calls the callbackfn function one time for each element in the array.
     * @param thisArg  An object to which the this keyword can refer in the callbackfn function.
     * If thisArg is omitted, undefined is used as the this value.
     */
    forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
}
1234567891011

What does it do? #

The Array.prototype.forEach method is one of the ways to iterate through an array in javascript. As stated in the documentation, it performs the specified action for each element in an array. It's like saying: "For each element in an array, execute this specified function." . For example let's take a look at the code  below:


//we have a simple array
const arr = [1,2,3,4].

//function that prints a value
function callbackFunction(value){
  console.log(value);
}

arr.forEach(callbackFunction);
//output:
//1
//2
//3

//in most cases, people just use an inline function or an arrow function like so:
arr.forEach(function(value){
   console.log(value);
})

arr.forEach((value) => {
   console.log(value);
})
1234567891011121314151617181920212223

For the example above, each value will be printed on the console.

Does forEach iterate over empty values? #

For each does not iterate over empty values but it iterates over undefined an null values.


//function that prints a value
arr.forEach((value) => {
   console.log(value);
})
//prints
//1
//2
//3
//4
//undefined
//null
123456789101112

What is the callbackfn parameter? #

As stated above, the callbackfn is executed for each element in the array.

It accepts 3 arguments: value, index, and the array itself. The index and array parameters are useful if you want to check for certain conditions when executing the action. In the example below we check if index is an even number and log the value.

The parameter values are provided by the forEach method, we just need to specify how to use the callback for each iteration. This a very important concept in functional programming in Javascript.


//we have a simple array
const arr = [1,2,3,4].

//only logs if the index is an even number
arr.forEach((value, index) => {
    if(index %2 === 0){
       console.log(value)
    }
})
//only logs the first and last value
arr.forEach((value, index, array) => {
   if(index === 0){
      console.log(value);
   }
   if(index === array.length - 1){
      console.log(value)
   }
})

let oddNumbers = [];
arr.forEach((value) => {
    if(value %2 === 1){
       oddNumbers.push(value);
    }
})
1234567891011121314151617181920212223242526

Does the callbackfn return any value? #

Based on the function definition, it does not return any value.

What happens if I use a return statement inside the callbackfn? #

Just like any other function, it terminates the remaining execution of the function. For example if we can modify the example above to skip certain .


const arr = [1,2,3,4].

arr.forEach((value, index) => {
    if(index %2 !== 0) return; //skip if index is odd
    console.log(value) //print the value if index is even
})
1234567

Is it possible to use async/await in the callbackfn? #

It's possible to use async/away inside the callbackfn however we must be aware of the caveats.

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const sleepTime = [4000, 3000, 2000, 1000];
//modify the callback function to async
arr.forEach(async (value, index) => {
  //we can now use await
  await sleep(sleepTime[index]);
  console.log(value);
});

//prints
//4
//3
//2
//1
1234567891011121314

Throwing an error inside an async callback function results in an unhandled promise rejection:

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function test(arr) {
  try {
    arr.forEach(async (val) => {
      await sleep(1000)
      // Unhandled promise rejection because `forEach()` doesn't return
      // a promise, so there's no way to `await`.
      throw new Error('Oops!');
    });
  } catch (err) {
    // Does not execute
  }
}
12345678910111213

Is is possible to use sequential Promise execution inside forEach? #

Let's say we want to call a REST api sequentially. You might think that we can use forEach but this doesn't work since forEach will execute an async callbackfn as if it's a normal function. For example if we want to get individual todos by id sequentially, determining which promise will execute first is impossible


const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function getTodosForEach() {
  let urls = [
    `https://jsonplaceholder.typicode.com/todos/1`,
    `https://jsonplaceholder.typicode.com/todos/2`,
  ];
  //arrow function always has empty object as this
  urls.forEach(async (url, index) => {
    const response = await fetch(url);
    const json = await response.json();
    console.log(json);
  });
}
document.getElementById('btn2').onclick = getTodosForEach;
123456789101112131415
Indeterminate execution of promises using forEach

For scenarios like this, it's better to use the for...of operator to ensure sequential execution of promises:


const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function getTodos() {
  let urls = [
    `https://jsonplaceholder.typicode.com/todos/1`,
    `https://jsonplaceholder.typicode.com/todos/2`,
  ];
  for (let url of urls) {
    const response = await fetch(url);
    const json = await response.json();
    console.log(json);
    await sleep(1000);
  }
}

getTodos();
12345678910111213141516

The result is sequential execution:

Sequential execution of promises using for..of statement

What is the difference between using an arrow function or an anonymous function for the callbackfn? #

In most cases, you can use either one depending on your preference or coding guidelines. The only thing that changes is the this operator. Using this operator inside an arrow function is different from an anonymous function and follows the rules of the javascript this operator.

Let's examine the differences:


//arrow function always has empty object as this
arr.forEach((value) => {
  console.log(this) //prints {}
})

//anonymous function prints Window as this. If we want to change it, we should use the optional thisArg parameter
arr.forEach(function (value) {
  console.log(this) //prints Window
})

const myObj = {prop1: "hello" }
arr.forEach(function (value) {
  console.log(this) //prints {prop1: "hello"}
}, myObj)

//note that using thisArg parameter for arrow function does not work
arr.forEach((value)=> {
  console.log(this) //it still prints {}
}, myObj)
1234567891011121314151617181920

Does forEach work for Iterables like Map.keys() or HTMLCollection? #

forEach is a method for arrays and does not work for Set or Map. To iterate over Set or Map objects, we have to transform it first using Array.from() method.


const map = new Map([['key', 'value']]);
// Throws "TypeError: map.keys(...).forEach is not a function"
map.keys().forEach(val => console.log(val));

//correct:
Array.from(map.keys()).forEach(val => console.log(val));

const elems = document.getElementsByTagName('div');
//Error: elems.forEach is not a function
elems.forEach((val) => console.log(val));

//correct:
Array.from(elems).forEach((val) => console.log(val));
1234567891011121314

Is it possible to use forEach from Map or Set classes? #

It is possible but Map and Set classes have a different forEach method from Array.forEach 

Is it possible to use break or continue inside forEach? #

Since callbackfn is just a plain javascript function it's not possible to use looping statements like skip or continue which will throw a runtime error

//Error: Illegal continue statement: no surrounding iteration statement

let arr = [1, 2, 3, 4];

arr.forEach((value, index) => {
  if(index === 0{
    continue;
  }
  
  console.log(value);
});

//Error: Illegal break statement
arr.forEach((value, index) => {
  if(index === 0){ 
    break;
  }
  console.log(value);
});
12345678910111213141516171819

An alternative to this is to use return statement or Array.filter method:

let arr = [1, 2, 3, 4];

//using return statement
arr.forEach((value, index) => {
  if(index === 0){ 
    return;
  }
  console.log(value);
});
//prints 
//2
//3
//4

//using filter
arr.filter((val,index) => index > 0).forEach(val => {
   console.log(val)
})
//prints 
//2
//3
//4
12345678910111213141516171819202122

Conclusion #

With that I guess we've covered everything there is to know about Array.prototype.forEach method, from its definition to its usage and its limitations. If there's anything lacking in this tutorial, please feel free to contact me through my email.

Thank you for reading! If you like tutorials like these please leave a like or subscribe to our newsletter to get tutorials directly to your inbox!

Resources: #

MasteringJS tutorial - forEach Fundamentals


Credits: Image by David Mark from Pixabay

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle