First, I'd like to lead this article with a confession:
Whenever I'm presented with a programming concept, I immediately start searching my brain for practical ways that I would use that concept. If I can't think of a practical application for that concept, I might technically "understand" it - but I'll never truly master it.
This can be a bit of a mental roadblock for me because someone says, "Hey, you should consider using [INSERT CONCEPT HERE]," and I nod and read a few basic articles about the concept. But unless I can envision a logical scenario where I'll use that concept, it just sits in the back of my brain, gathering dust, and doing me little good in daily programming endeavors.
For many years, the Immediately Invoked Function Expression (IIFE) was exactly this type of concept. Sure, I understood what an IIFE was. But they never really "clicked" for me because there was never any time when I thought, "Oh, wait. I should use an IIFE here!"
For whatever reason, I've actually started using IIFEs recently with some regularity. So I figured I'd write up this article because it might help others who've never really grasped their utility.
What is an IIFE?
The fully-articulated name of an IIFE basically tells you what it is. It's a function expression that's... immediately invoked. It differs from an anonymous function in that anonymous functions can still be called numerous times, like this:
[1,2,3].forEach(item => console.log(item))
In the example above, this is the anonymous function:
item => console.log(item)
It's "anonymous" because it's not stored in a variable. So it has no "handle" that we can use to manually call the function at other points in our code. But it's not an immediately invoked function expression because it's not run... immediately. Instead, it is run once for each item that's passed into the Array prototype function .forEach()
.
IIFEs have a special syntax that tells the JavaScript engine to run the logic inside them immediately after they're defined. That syntax looks like this:
const item = 1;
(() => console.log(item))()
In the example above, the IIFE will console-log the value of item
(1
). And that's fine, except... When I look at an example like the one shown above, the IIFE just feels utterly... pointless. I mean, if all you want to do is console-log the value of item
, then why wouldn't you just do this?
const item = 1;
console.log(item);
Of course, for this (totally simplistic) example, there really is no logical reason to use an IIFE. And it's that basic bit of common sense that always led me to write off IIFEs as a near-pointless language construct.
To be clear, I've occasionally encountered IIFEs that were written by other devs. But sooooo many times, when I see IIFEs "in the wild", I still end up scratching my head as to why the original developer even chose to use it in the first place. My thinking went like this:
IIFEs are just a block of immediately-invoked code.
So if I want that code to be immediately-invoked, then why wouldn't I simply... write the code - without wrapping it inside an IIFE???
But as I stated above, I've found several scenarios where they can truly be useful. I hope that the following examples will help you to grasp their utility as they have for me.
Libraries
Let's say that you have a single file that contains a "library" of utility functions. That may look something like this:
// conversionUtilities.js
const convertUserDBToUI = userDB => {
return {
id: userDB.user_id,
firstName: userDB.first_name,
lastName: userDB.last_name,
}
}
const convertUserUIToDB = userUI => {
return {
user_id: userUI.id,
first_name: userUI.firstName,
last_name: userUI.lastName,
}
}
The functions above simply take objects that are formatted for the UI and converts them into objects that are formatted for the DB (and vice versa).
Of course, if these are truly meant to be "utility" functions (meaning that you'll probably need to call on them from various places throughout your application), you'll need to export
these functions so they can be imported at other places in the app. That would look like this:
// conversionUtilities.js
export const convertUserDBToUI = userDB => {
return {
id: userDB.user_id,
firstName: userDB.first_name,
lastName: userDB.last_name,
}
}
export const convertUserUIToDB = userUI => {
return {
user_id: userUI.id,
first_name: userUI.firstName,
last_name: userUI.lastName,
}
}
The code above would work just fine. However, it may lead to some extraneous import
statements throughout your code, because anytime you need to use two-or-more of these functions in a single file, you'll need to include two-or-more import
statements in those files. When you're creating "library" files that contain many related utility functions, it can often be useful to have them contained within a single export
.
One way you could accomplish this is with a class
:
// conversionUtilities.js
export const Convert = class {
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,
}
}
}
Once again, the code above works just fine. Here are some benefits of the above code:
We no longer need to repeat
convert
in every one of the method names, because it's implied in the name of the class.All of our utility functions are "bundled" inside a single entity - the
Convert
class - so they don't need to be imported individually.
But there are also a few "downsides":
Some (nay... many) JS/TS developers simply abhor using the
class
keyword.Anytime you need to use these utility functions, you first need to call something like
const convert = new Convert();
before using the utility functions likeconst userUI = convert.userDBToUI(userDB);
. That could become... cumbersome.
You could solve the second "problem" by exporting an instance of the class, rather than exporting the class itself. That would look like this:
// conversionUtilities.js
const Convert = class {
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,
}
}
}
export const convert = new Convert();
That saves us some keystrokes whenever we're importing the Convert
class. But it still rubs some devs the wrong way because we're relying on classes. But that's OK. Because we can accomplish the same thing with a function:
// conversionUtilities.js
export const Convert = () => {
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,
}
}
return {
userDBToUI,
userUIToDB,
}
}
Here are the benefits of this approach:
No more "yucky"
class
keyword.Again, we no longer need to repeat
convert
in every one of the function names, because it's implied in the name of the exported function.All of our utility functions are "bundled" inside a single entity - the
convert
function - so they don't need to be imported individually.
But there's still at least one "downside":
- Anytime you need to use these utility functions, you first need to call something like
const convert = Convert();
before using the utility functions likeconst userUI = convert.userDBToUI(userDB);
. That could become... cumbersome.
You could solve this "problem" by exporting the invocation of the parent function, rather than exporting the parent function itself. That would look like this:
// conversionUtilities.js
const Convert = () => {
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,
}
}
return {
userDBToUI,
userUIToDB,
}
}
export const convert = Convert();
Honestly, this is the primary way that I usually see this done. But there is another way - with an IIFE - that we could do this without having to first 1) define the Convert
function, and then 2) export the invocation of that function. That would look like this:
// conversionUtilities.js
export const convert = (() => {
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,
}
}
return {
userDBToUI,
userUIToDB,
}
})()
Notice here that we didn't have to define the parent function, and then export the invocation of that function. Instead, we used an IIFE to do it all in one statement. Now, whenever someone imports convert
, they'll get an object that already contains all of the utility functions inside convert
.
Is this some rock-solid no-brainer use case for IIFEs??? No. As stated above, you can accomplish the same thing by:
Exporting every single utility function individually.
Encompassing the utility functions inside a class, and then exporting an instance of that class.
Encompassing the utility functions inside a parent function, and then exporting the invocation of that function.
Nevertheless, I find the IIFE approach to be just a little bit cleaner. And it's at least one valid use case for IIFEs.
Swallowing Promises
Async/await can be powerful tools when dealing with data calls and other asynchronous actions. But they can also cause a bit of a cascading headache in your code - especially in strict TypeScript code.
await
can only be used inside an async
function. Imagine that you have three cascading functions that do the following:
FunctionA handles the result of a user action (like clicking a "Submit" button).
If the right conditions are met, FunctionA then calls some validation logic in FunctionB.
Based on the results of the validation, FunctionB may then call FunctionC, which transmits the data to the server via an asynchronous REST call.
You want to use the await
syntax in FunctionC, which means that you need to define it as an async
function.
But this means that FunctionB will now be expecting a promise from FunctionC. So... you change FunctionB to be an async
function.
But that means that FunctionA will now be expecting a promise from FunctionB. So... you change FunctionA to be an async
function.
But now the event handler that originally called FunctionA is also expecting a promise. And depending on how strict your TypeScript environment is configured, that simply may not be an option.
The "cascading" effect of async/await
would look something like this:
export const App = () => {
const submit = () => {
const result = await someApiCall(values);
// handle API result
return result;
}
const doValidation = () => {
// check some validation
if (isValid) {
return submit();
}
}
const handleSubmit = () => {
// set some state vars
if (someCondition) {
return doValidation();
}
}
return <>
<button onClick={handleSubmit}>
Submit
</button>
</>
}
In the example above, the TypeScript compiler will start complaining because callApi()
is not an async
function. So you set callApi()
to be async
but... that also requires doValidation()
to be async
. So you set doValidation()
to be async
but... that also requires handleSubmit()
to be async
. So you set handleSubmit()
to be async
but... TypeScript might still complain because the onClick
event handler is not configured to handle the resulting promise.
At this point, you've started to shove async
into a lot of places where you really never wanted it to be. And to make it even worse, TypeScript will still complain about the fact that your onClick
handler is not handling the resulting promise.
[NOTE: In plain ol' vanilla JavaScript, you can simply ignore the resulting promise. But you can't convert your entire project from TypeScript to JavaScript just because you don't wanna deal with all of these cascading usages of async/await
.]
Luckily, an IIFE can do a load of good here. That would look like this:
export const App = () => {
const callApi = () => {
(async () => {
const result = await someApiCall(values);
// handle API result
return result;
})()
}
const doValidation = () => {
// check some validation
if (isValid) {
return callApi();
}
}
const handleSubmit = () => {
// set some state vars
if (someCondition) {
return doValidation();
}
}
return <>
<button onClick={handleSubmit}>
Submit
</button>
</>
}
In the code above, we can now use async/await
inside callApi()
without ever having to declare callApi()
as an async
function. This works because the asynchronous call is happening inside an async
function. It just so happens that the async
function is... an IIFE inside the callApi()
function.
This is actually a use case for IIFEs that I use a lot.
In-place Logic
Finally, I wanna illustrate a scenario where in-place logic (i.e., the kind of logic that's provided by an IIFE) can make a lot of sense.
I've recently been helping a friend with a bunch of EDI data transformations. On the surface, the work is pretty simple. You get a big data object that's supposed to be in a given format - maybe something like this:
const rawData = {
fee: 'fee',
einz: '1',
fie: 'fie',
zwei: '2',
foe: 'foe',
drei: '3',
fum: 'fum',
}
Then you have to write some transformation routine that will extract (and "massage") certain values and return a new data object in a different format, like this:
const getGiantSpeak = data => {
return {
fee: data.fee,
fie: data.fie,
foe: data.foe,
fum: data.fum,
}
}
The "problem" arises when you realize that many of the vendors supplying the data will format that data in many different ways. For example, the simple logic above works just fine as long as you assume that the values in rawData
are simple scalar values.
But then you find that your data transformations are intermittently failing. When you investigate the intermittent problems, you realize that sometimes the vendor is submitting rawData.fie
as a simple string. And other times they're submitting it as an array of strings. So to fix this, you need to insert a little bit of logic at the point where getGiantSpeak()
is returning the fie
value.
In this scenario, I find that a simple IIFE works wonders:
const rawData = {
fee: 'fee',
einz: '1',
fie: ['fie', 'fiddler'],
zwei: '2',
foe: 'foe',
drei: '3',
fum: 'fum',
}
const getGiantSpeak = data => {
return {
fee: data.fee,
fie: (() => {
if (Array.isArray(data.fie))
return data.fie.join(',');
else
return data.fie;
})(),
foe: data.foe,
fum: data.fum,
}
}
[NOTE: I realize that you can also accomplish the above logic with a simple ternary operator. But the "real life" examples usually require some more "nuanced" logic that doesn't lend itself to a ternary operator.]
In this scenario, the logic needed to build the new object is truly single-use, and it can be much cleaner to encompass it in an IIFE.
Top comments (22)
Nice post! From my side:
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.
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.
That's a good point. Thanks for the catch!
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
andlet
. This is why, for many years, I just didn't see a need to use an IIFE - because I haven't written a singlevar
in 8 years. So this closure functionality meant nothing to me in the way of useful features.It is true that
const
andlet
are block-scoped local variables. I just mentioned encapsulation for historical reasons in relation to thevar
.In the statement:
the function
(i)=>console.log(i)
is not regenerated for each iteration of the loop, it is scoped to the surrounding block.@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.
Hmmm, I disagree. Here's an implementation of forEach as an example:
Clearly the function is created and passed once.
@miketalbot How "function is created and passed once" is then determined?
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:
Then its the same thing.
You can prove the point by a really overly contrived example:
@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.
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 :)
@miketalbot
You're reading in my mind ;-)
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.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.
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
andlet
. There's another comment in this thread that also points this out.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".
Thank you!
I believe one of the most valuable use cases for Immediately Invoked Function Expressions (IIFE) is within the React
useEffect
hook. In theuseEffect
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:In this example, the IIFE
(async () => { /* ... */ })()
is used within theuseEffect
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 auseEffect
hook, where direct use ofasync
functions is not supported.The first example, you can just export an object.
or
And another use case that comes to mind is to avoid global variable conflicts:
Just wrap each piece of code in IIFE or just a block to soved this.
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.
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!
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()}
Awesome read !
nice 👍 thanks
Nice read!