DEV Community

Cover image for Use Cases for IIFEs

Use Cases for IIFEs

Adam Nathaniel Davis on September 13, 2023

First, I'd like to lead this article with a confession: Whenever I'm presented with a programming concept, I immediately start searching my brai...
Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

Nice post! From my side:

Instead, it is run once for each item that's passed into the Array prototype function .forEach().

In each loop, the function is destroyed and created again. Some browsers may optimise it internally, but it depends on the engine. A better way, imho, is to pass the reference and name the function with a description of what it does.

collection.forEach(addRedBackgroundColor);
Enter fullscreen mode Exit fullscreen mode

I'd add that one of the primary purposes of an IIFE is to encapsulate variables and functions, preventing them from polluting the global scope. By wrapping code within an IIFE, the variables and functions defined within it are not accessible from outside the function's scope.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

In each loop, the function is destroyed and created again.

That's a good point. Thanks for the catch!

I'd add that one of the primary purposes of an IIFE is to encapsulate variables and functions

Yeah. Maybe I should've mentioned this because, IMHO, IIFEs used to have a primary purpose in creating closures. But with modern JS standards, there's no pollution of the global scope so long as you're using const and let. This is why, for many years, I just didn't see a need to use an IIFE - because I haven't written a single var in 8 years. So this closure functionality meant nothing to me in the way of useful features.

Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

It is true that const and let are block-scoped local variables. I just mentioned encapsulation for historical reasons in relation to the var.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

In the statement:

[1, 2, 3].forEach((i)=>console.log(i))
Enter fullscreen mode Exit fullscreen mode

the function (i)=>console.log(i) is not regenerated for each iteration of the loop, it is scoped to the surrounding block.

Collapse
 
cezarytomczyk profile image
Cezary Tomczyk

@miketalbot
On each iteration the function is created and destroyed. Whenever you call a function (in this case, when forEach calls its callback), a new execution context is created.

Scope pertains to the visibility of variables, and context refers to the object within which a function is executed.

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐

Hmmm, I disagree. Here's an implementation of forEach as an example:

function forEach(array, fn) {
    for(let i = 0, l = array.length; i < l; i++) {
       fn(array[i])
    }
}

forEach([1,2,3], (i)=>console.log(i))
Enter fullscreen mode Exit fullscreen mode

Clearly the function is created and passed once.

Thread Thread
 
cezarytomczyk profile image
Cezary Tomczyk

@miketalbot How "function is created and passed once" is then determined?

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐

There will be an execution context for any function invocation. The i=>console.log(i) is instantiated in the function calling forEach (and a closure for the execution context could have been created there, but wasn't in my example). The loop inside forEach is just calling that previously instantiated function. An execution context is created on the stack for each function call, but the function is the same one. It wouldn't be different if this were an arrow function or a real function.

If you were just to write:

function forEach(array, fn) {
    for(let i = 0, l = array.length; i < l; i++) {
       fn(array[i])
    }
}
const iteratorFn = (i)=>console.log(i)
forEach([1,2,3], iteratorFn)
Enter fullscreen mode Exit fullscreen mode

Then its the same thing.

You can prove the point by a really overly contrived example:


let index = 0

function forEach(array, fn) {
    for(let i = 0, l = array.length; i < l; i++) {
       fn(array[i])
       console.log(fn.$index)
    }
}

function createIterator() {
   const iterator = (i)=>console.log(i)
   iterator.$index = index++
   return iterator
}

forEach([1,2,3], createIterator())

Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
cezarytomczyk profile image
Cezary Tomczyk • Edited

@miketalbot I still think that those are two different thing:

.1. collection.forEach(addBackgroundColor) and .2. collection.forEach((item) => { item.backgroundColor = '#000'; })

And browsers may optimise #2 internally. I couldn't find in ECMAScript details around passing references vs. anonymous functions. So, I think it's the browser maker's implementation details. Also, you can make some performance tests and see that for both cases, you'll get different results. Sometimes, surprisingly, #2 becomes faster than #1. I haven't measured memory consumption.

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐

Interesting that there's a difference. I'll check it out. The reason I do this is that I have places where I do decorate the function with extra attributes used later (in one odd case) and so I'm sure I still have the same object - however - I can see that the compiler could optimise it when its inline - this could provide the performance gain perhaps? I'll try to give it a go and look at memory profiles myself at some point. I think we've both been hunting for the same documentation :)

Thread Thread
 
cezarytomczyk profile image
Cezary Tomczyk

@miketalbot

I think we've both been hunting for the same documentation :)

You're reading in my mind ;-)

Collapse
 
moopet profile image
Ben Sinclair

I think this is a good article.

But I also think that modern Javascript seems to go out of its way to be unreadable, and IIFEs add more clutter to that. Especially given how a lot of people choose to prettify their code, seeing stuff like }})() is an eyesore as far as I'm concerned.

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

I still think that IIFEs were very useful in JavaScript 15y ago. I haven't cross a real case for them since ES6.

Your export const IIFE used to avoid a class can be used simply by exporting each function. Using ES modules you can easily create singletons in JS and avoid classes or whatever.

But GGOOD ARTICLE though! 🎉 Yes we need to apply concepts in order tonlearn them.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I still think that IIFEs were very useful in JavaScript 15y ago.

Yes. This is absolutely true. I should've done a better job of explaining that upfront. The "original" use case for IIFEs was for closures - which... kinda flew out the window once we had const and let. There's another comment in this thread that also points this out.

Your export const IIFE used to avoid a class can be used simply by exporting each function. Using ES modules you can easily create singletons in JS and avoid classes or whatever.

I don't disagree with you at all. And to be clear, if you never write an IIFE in your modern code, I'm perfectly OK with that. I personally think that the async/await example is much more applicable for "modern" dev. But again, I'm not trying to say that you should be using IIFEs all over the place. It's truly a design choice.

I will say this though: I do appreciate the ability to group utility functions together (i.e., libraries). Personally, I don't like opening a JS/TS file and finding that it has 20 different exports of 20 different unique functions. I personally adhere to the idea of 1 file === 1 export. But I'm not trying to claim that my preference is "right".

But GGOOD ARTICLE though! 🎉 Yes we need to apply concepts in order tonlearn them.

Thank you!

Collapse
 
rampa2510 profile image
RAM PANDEY

I believe one of the most valuable use cases for Immediately Invoked Function Expressions (IIFE) is within the React useEffect hook. In the useEffect hook, you cannot directly run an asynchronous function. Instead, you can declare an asynchronous IIFE and then execute it.

Here's an example in a React component that demonstrates the use of an IIFE in the useEffect hook to fetch data asynchronously:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Using an async IIFE to fetch data
    (async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const jsonData = await response.json();
        setData(jsonData);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    })();
  }, []);

  return (
    <div>
      {data ? (
        <p>Data: {JSON.stringify(data)}</p>
      ) : (
        <p>Loading data...</p>
      )}
    </div>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

In this example, the IIFE (async () => { /* ... */ })() is used within the useEffect hook to fetch data asynchronously and update the component's state when the data is received. This pattern allows you to work with asynchronous code in a useEffect hook, where direct use of async functions is not supported.

Collapse
 
jinliming2 profile image
Liming Jin • Edited

The first example, you can just export an object.

export const convert = {
  userDBToUI: userDB => {
    return {
      id: userDB.user_id,
      firstName: userDB.first_name,
      lastName: userDB.last_name,
    }
  },
  userUIToDB: userUI => {
    return {
      user_id: userUI.id,
      first_name: userUI.firstName,
      last_name: userUI.lastName,
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

or

const userDBToUI = userDB => {
  return {
    id: userDB.user_id,
    firstName: userDB.first_name,
    lastName: userDB.last_name,
  }
}

const userUIToDB = userUI => {
  return {
    user_id: userUI.id,
    first_name: userUI.firstName,
    last_name: userUI.lastName,
  }
}

export const convert = { userDBToUI, userUIToDB };
Enter fullscreen mode Exit fullscreen mode

And another use case that comes to mind is to avoid global variable conflicts:

const item = 1;
console.log(item);

// long code, or another script tag, libraries

const item = 2; // conflict
console.log(item);
Enter fullscreen mode Exit fullscreen mode

Just wrap each piece of code in IIFE or just a block to soved this.

// IIFE
(() => {
  const item = 1;
  console.log(item);
})();

// long code, or another script tag, libraries

// just a block
{
  const item = 2;
  console.log(item);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Yes, you are correct. I will say that, for my programming "tastes", I don't like having an object that has many different functions defined inline. But I'm also not trying to claim that such an approach is "wrong".

Rather, I was just trying to highlight some areas where an IIFE might make some sense.

Collapse
 
jorensm profile image
JorensM

Thanks for the great article, I enjoyed reading it!

My most used scenario for IIFEs is the async once. But I hadn't thought about your last suggestion, that's a great one as well!

As for the first one - you could just store your functions in an object and export that, then there's no need to instantiate it.

Thanks for the article again!

Collapse
 
cstroliadavis profile image
Chris

So, a bunch of folks have mentioned this. The main use case for iifes was to prevent pollution of the global namespace.

The other is too allow the same variable names to be used repeatedly (perhaps you're downloading and running the same script repeatedly).

With modern JavaScript, you can accomplish the same thing more easily by simply wrapping your code in braces.

{sameAsAnIife()}

Collapse
 
kenneth_sidibe profile image
Kenneth

Awesome read !

Collapse
 
c4miloarriagada profile image
Camilo Arriagada Vallejos

nice 👍 thanks

Collapse
 
mitchiemt11 profile image
Mitchell Mutandah

Nice read!