A JavaScript IIFE (Immediately Invoked Function Expression) is a function that runs the moment it is invoked or called in the JavaScript event loop. Having a function that behaves that way can be useful for several use cases. In this tutorial, you will learn about the benefits of using an IIFE (pronounced “iffy”) and when to use them instead of traditional functions. You will also write tests for your functions and integrate continuous integration/continuous deployment (CI/CD) for these tests.

Prerequisites

To follow along, you will need:

  • A GitHub account.
  • A CircleCI account.
  • NodeJS installed locally.
  • Basic understanding of JavaScript, Git, and unit testing.

Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.

How IIFEs work

IIFEs prevent pollution of the global JS scope. In a traditional function, if you create a variable within the function, it is accessible in the global object. If you define a variable in an IIFE, it is accessible only directly within the function.

For example, the library jQuery has the object $. If you have another module that also imports this object, you may run into some confusion and errors. An IIFE eliminates those errors. You can use an IIFE to create a scope for only jQuery and its methods.

Another benefit of using IIFEs is performing asynchronous operations such as the setTimeout() method. You will have a chance to use this later in the tutorial in the section about closures.

IIFEs can also be used to create private variables. These variables are useful in cases where you may need to prevent accidental modifications or changes to important values.

How IIFEs work

Setting up the application

Clone the repository for the tutorial. Open a terminal and run this command:

git clone https://github.com/CIRCLECI-GWP/javascript-iife.git`

After cloning the repository, change the directory to the project directory by running:

cd  javascript-iife

You will also need to install the required dependencies by running this command:

npm install

Using IIFEs in functions

IIFEs can be used for different purposes. One of the main uses of these functions is declaring global variables.

This code snippet shows how IIFE can be used for global variable declaration:

// Global variable declaration
<script>
  (function ($){
     let counter = 1;
      $("#increment").click(function () {
        $(this).text("Number of clicks: " + counter++);
      })
  })(jQuery)
</script>

By using jQuery this way, you have access to the $ object. Any code inside the IIFE that uses the object will only refer to jQuery. This object then gives you access to jQuery functions without cluttering the global namespace.

It is also important to understand that IIFEs are functions themselves. The only difference is that they do not need to wait for invocation. On further investigation, you can see that in this snippet:

(function myFunc() {
  // Beginning of function
  console.log(greeting);
})(); // End of function //calls the function execution e.g. myFunc()

Semicolons before IIFEs

One thing to be careful with is variables that are not closed off or that JavaScript deems to not have been closed off. You may run into errors if you omit semicolons, especially in applications using IIFEs. Here is an example:

const greeting = "Hello world"(function () {
  console.log(greeting);
})();

If you run this, you will get an error.

Error code

For unterminated statements like variables, Automatic Semicolon Insertion (ASI) and bundlers like webpack will prevent these types of errors. However, they don’t work for all cases, including IIFEs. You can bypass this by either adding a semicolon at the end of the statement or at the beginning of the IIFE. This code snippet executes without errors because you closed off the variable:

const greeting = "Hello world";
(function () {
  console.log(greeting);
})();

Named vs anonymous IIFEs

You can name your IIFEs or leave them anonymous. Be aware that naming them does not mean that they will be invoked after they are executed. Naming can be useful, especially if you have several IIFEs that perform different operations close to each other. This code snippet shows an example of a named IIFE:

const greeting = "Hello world";

(function myGreeting() {
  console.log(greeting);
})();

This function is named IIFE, which does not give it an advantage over an unnamed IIFE. An unnamed IIFE has no function names and only the function keyword. Here is the structure of an anonymous IIFE:

(function () {
  //Your code goes here
})();

The syntax is similar to JavaScript’s anonymous functions. You can also use the arrow function variation:

(() => {
  //Your code goes here
})();

Using IIFEs with closures

Another important use of IIFEs is closures. Closure is the ability of a function to use variables or other information outside its local scope. For instance, the function from the previous snippet displayed the greeting even though its value is defined outside the function. This example shows how IIFEs work with closures:

const friendlyFunction = (function () {
  let greetCount = 0;
  return function () {
    console.log(`Hello ${greetCount}x`);
    return greetCount++;
  };
})();

friendlyFunction();
friendlyFunction();
friendlyFunction();

In this code snippet, you define the IIFE and store it in a variable, friendlyFunction. As mentioned earlier, IIFEs are executed when they are defined. In this case though, the inner function is not executed until friendlyFunction() is called. If you comment out the function calls at the bottom, there will be no output on the console.

The greetCount variable is private and accessible only in the IIFE. When invoking the function severally, the value of greetCount variable is persisted and incremented with each call. Without closure, the value would always be 0.

Closures in IIFEs ensure that the global namespace is not polluted. That’s true even if you name your IIFE variables using the names of variables that exist in the global scope.

Integrating CircleCI

The previous section described some of the ways that IIFEs can be useful. That list cannot be complete without writing tests and integrating them with CI/CD best practice.

To write tests for your IIFE functions, you will use Jest, a JavaScript test framework.

Under users.js in the cloned repository, you have this asynchronous IIFE that gets users from a free API. Here is what you will use for this section:

const axios = require("axios");

//Asynchronous IIFEs
const getUsers = (async () => {
  async function fetchUsers() {
    const users = await axios
      .get("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.data)
      .catch((err) => console.log(err));
    return users;
  }

  return {
    array: await fetchUsers(),
  };
})(); // should get users when we execute `node users.js`

module.exports = getUsers;

This IIFE returns an array containing 10 users. You can use the data from this array to write tests to make sure you get the correct array of users:

const getUsers = require("./users");

describe("JavaScript IIFE test", () => {
  test("should get the users array", async () => {
    const response = await getUsers;
    expect(response).not.toBeNull();
    expect(response.array).not.toBeNull();
    expect(response.array.length).toBe(10);
    expect(response.array).toBeInstanceOf(Array);
    expect(response.array[0]).toBeInstanceOf(Object);
  });

  test("should get a single user", async () => {
    const response = await getUsers;
    expect(response).toBeDefined();
    expect(response.array[0].name).toEqual("Leanne Graham");
  });
});

This code snippet has a test suite that uses Jest to ensure that the data you receive is as expected. This code can be found in the app.test.js file. Run this test file locally:

 npm run test

If your assertions are correct, the tests should pass as shown here:

PASS  ./app.test.js
  JavaScript IIFE test
    √ should get the users array (1563 ms)
    √ should get a single user (10 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.956 s
Ran all test suites.

Integrating CircleCI

CircleCI is a CI/CD tool that helps automate your workflows and run your tests.

In the root folder of your project, create a .circleci folder. Add a config.yml file inside it. In the config.yml file, add this:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: cimg/node:21.4.0
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: install dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: JavaScript IIFE test
          command: npm test
      - store_artifacts:
          path: ~/iife-JavaScript

CircleCI creates a virtual environment for pipeline execution. This code snippet installs the necessary dependencies, saves the cache for faster builds, and then executes your tests.

Save your local changes, commit and push them to GitHub. Review Pushing a project to GitHub for step-by-stepinstructions.

Sign in to your CircleCI account. From the Projects tab, click Set Up Project next to the name of your remote repository.

Project setup

When prompted, select Fastest, then click Set Up Project.

Config.yml

CircleCI will then run the build job successfully.

Successful build

Conclusion

In this tutorial, you have learned how JavaScript Immediately Invoked Function Expressions (IIFEs) can make workflows faster with immediate invocations. You also learned about some use cases for IIFEs and the problems they solve. Finally, you wrote tests and integrated CircleCI. I hope you enjoyed reading this tutorial and until the next one, keep learning!


Waweru Mwaura is a software engineer and a life-long learner who specializes in quality engineering. He is an author at Packt and enjoys reading about engineering, finance, and technology. You can read more about him on his web profile.

Read more posts by Waweru Mwaura