Embracing Immutability in Asynchronous JavaScript Functions: Conditional Async Calls

2023-02-08

#development#frontend
Embracing Immutability in Asynchronous JavaScript Functions: Conditional Async Calls

You need to fetch data, but only sometimes. The condition is known at runtime. Your instinct is to reach for let, set it to null, and conditionally reassign it inside an if-block.

It works. But it's mutable, it's ugly, and it splits your function into "before the condition" and "after the condition" in a way that makes the flow harder to follow.

There's a cleaner approach, and it takes three lines.

The Mutable Way

Here's what most developers write without thinking twice:

async function fetchData() {
  const data = await fetch('https://api.example.com/data');
  return data.json();
}
async function mainFunction(inputCondition) {
  let data = null;

  if (inputCondition) {
    data = await fetchData();
  }

  // Continue with the main function flow
  // 'data' will be either the fetched data or null
}

The let sits there, mutable, waiting to be reassigned. The main function flow is interrupted by a conditional block that mixes async concerns with business logic. It's not terrible. But it's also not as clean as it could be.

The Immutable Way

Abstract the conditional logic into a function that always returns a promise:

async function fetchData() {
  const data = await fetch('https://api.example.com/data');
  return data.json();
}
function conditionalFetch(condition) {
  return condition ? fetchData() : Promise.resolve(null);
}

Now your main function stays flat, immutable, and readable:

async function mainFunction(inputCondition) {
  const result = await conditionalFetch(inputCondition);
  // Continue with the main function flow
  // 'data' will be either the fetched data or null
}

No let. No reassignment. No conditional block cluttering the main flow. result is const — assigned once, never changed.

Why This Matters

The difference seems cosmetic on a three-line example. It's not. In real codebases, conditional async calls compound. You end up with two or three let variables, nested if-blocks, and a main function that reads like a choose-your-own-adventure book.

By abstracting each conditional operation into its own function that returns a promise, your main flow becomes a sequence of const declarations. Each one is self-documenting. Each one is testable in isolation.

The pattern works for any conditional async operation: fetch-or-default, create-or-skip, validate-or-proceed. Once you see it, you'll wonder why you ever reached for let.

The cleanest async code is the code where every variable is assigned exactly once.