Micro-pattern: IIFE and Return-Early

rkkautsar

Rakha Kanz Kautsar

Posted on March 8, 2021

Micro-pattern: IIFE and Return-Early

Immediately Invoked Function Expression (IIFE)

If you came from JavaScript background you will most likely have encountered this pattern. Remember JQuery?

;(function($) {
    $('.spoilerBtn').click(function() {
        var button = $(this).find('.spoiler').toggle();
    });
})(jQuery);
Enter fullscreen mode Exit fullscreen mode

I remember back then I used think of this as a "wrapper" of my JQuery code and go on with my life without trying to find out what it's actually doing. So let's deconstruct this code together.

IIFE illustration

As its name, this wrapper is actually just a function that is immediately executed. You can see the function is declared anonymously (without a name) inside the first bracket, then immediately executed with jQuery as its parameter (converted to $ inside the function body). As a side note, the function is also a closure, meaning that it can have access to all the variables of its parent (the one that declare it).

Really, it's just a bunch of shortcut to achieve this same code:

function main($) {
    $('.spoilerBtn').click(function() {
        var button = $(this).find('.spoiler').toggle();
    })
}

main(jQuery);
Enter fullscreen mode Exit fullscreen mode

So why do we want to use it then?

I myself also don't quite understand why I want to use this pattern instead of the more readable counterpart, but then I realize I can do things like this:

const user = getUser();

const color = (() => {
  switch (user.role) {
    case user.ADMIN:
      return 'red';

    case user.STAFF:
      return 'blue';

    default:
      return 'black'
  }
})();

const secretKey = (() => {
  if (!user) return null;
  if ([user.ADMIN, user.STAFF].includes(user.role)) return null;

  return expensivelyComputeSecretKeySync(user.id);
})();
Enter fullscreen mode Exit fullscreen mode

And perhaps my most used use case for this pattern is to quickly create an async task inside any scope:

function useFetchItem(itemId: number) {
  const dispatch = useDispatch();

  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;

    (async () => {
      try {
        const response = await fetchItem(itemId, { signal });
        dispatch(fetchItemSuccess(response));
      } catch (e) {
        if (e.name === 'AbortError') {
          console.log(`fetch for ${itemId} aborted!`);
        }
      }
    })();

    return () => {
      controller.abort();
    }
  }, [dispatch, itemId]);
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it's kind of useful as a one-shot logical block. I find it useful where I need to do some logic that is quite short and specific. If I instead write a utility function for this, I found it might distract the reader by forcing them to context switch (to a distant function or maybe even another "utils" file). I also need to pass in the parameters and add more things to remember while context switching. With IIFE, the reader can just read the code naturally from top to bottom.

Oh and I have to note that this is not only a JavaScript thing by any way. For example you can also do IIFE in Go (and most languages supporting closures, I think?). In fact, it can be spotted in many part of gobyexamples . For example here's from Signals demonstrating it's use to create a background goroutine listening to OS signals:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {

    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}
Enter fullscreen mode Exit fullscreen mode

See? It's quite useful for a short, concise, specific logic, right? Later if turns out it needs to be used anywhere else we can always just take it out and refactor it as separate function (and if needed, pass any parent variables as parameters).

Return-early pattern

a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. (Wikipedia)

Return-early illustration

While this is a pretty common pattern used in system design context as mentioned by Martin Fowler in Fail Fast , I found this can also be derived to a good micro-pattern to use anytime. My explanation for this is simply: return/throw immediately when something may go wrong so we can always follow the happy path.

Imagine we have something like this (exaggerated example):

async function handleRegister(username, password) {
  if (username && password) {
    try {
      const passwordValidation = validatePassword(password);
      const existingUser = await getUser(username);

      if (!existingUser && passwordValidation.ok) {
        const user = new User(username, password);
        await user.save();
        return user.id
      } else if (existingUser) {
        throw new ValidationError('Username already exists');
      } else if (!passwordValidation.ok) {
        throw new ValidationError(passwordValidation.message);
      }

    } catch (e) {
      throw e;
    }
  } else {
    throw new ValidationError('Username and password is required');
  }
}
Enter fullscreen mode Exit fullscreen mode

Wouldn't it be easier to digest if instead we return early on any possible error? As a bonus, we also get lesser nesting, it's easier to see the edge cases, and the code flows in one direction, making it easier to review.

async function handleRegister(username, password) {
  if (!username || !password) throw new ValidationError('Username and password is required');

  const passwordValidation = validatePassword(password);
  if (!passwordValidation.ok) throw new ValidationError(passwordValidation.message);

  const existingUser = await getUser(username);
  if (existingUser) throw new ValidationError('Username already exists');

  const user = new User(username, password);
  await user.save();
  return user.id
}
Enter fullscreen mode Exit fullscreen mode

So that's it, these two are the micro-patterns that I find are used pretty often, not only by me but also by others. Oh and these two are not mutually exclusive, they can also be used together. Personally I think this pattern can help to make our codes to be easier to reason so it benefits our future selves and also the ones reviewing our codes.

Let me know if you have other micro-patterns you usually use!

💖 💪 🙅 🚩
rkkautsar
Rakha Kanz Kautsar

Posted on March 8, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related