DNA⚡ JavaScriptAsync Patterns
ðŸĶ–DinosaurJavaScriptAsyncArchitecture

Async Patterns

Promises, async/await, and concurrency control are table stakes for senior frontend roles. Know the patterns that separate production code from tutorial code.

Async Patterns

Senior interviews don't ask "what is a Promise." They ask you to implement retry logic, handle race conditions, or design a concurrent task queue. This is the level you need.

Promise Fundamentals That Actually Matter

A Promise is a state machine: pending → fulfilled or pending → rejected. Once settled, it's immutable.

const p = new Promise((resolve, reject) => {
  resolve('first');
  resolve('second'); // ignored — already settled
  reject('error');   // also ignored
});

Microtask Queue

Promise callbacks (.then, .catch, .finally) run as microtasks — they execute before the next macrotask (setTimeout, I/O), but after the current synchronous call stack completes.

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2

Async/Await Is Not Just Sugar

async/await transforms sequential-looking code into promise chains, but it introduces behaviors you need to reason about:

async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json(); // returns a Promise — await is implicit for caller
}

The Unhandled Rejection Trap

async function loadData() {
  const user = fetchUser(1);   // no await — starts the promise
  const posts = fetchPosts(1); // starts concurrently
  return { user: await user, posts: await posts };
}

If fetchPosts rejects before fetchUser resolves, you get an unhandled rejection because the rejection happens while we're still awaiting fetchUser. Use Promise.all instead:

async function loadData() {
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts(1),
  ]);
  return { user, posts };
}

Concurrency Combinators

MethodSettles whenUse case
Promise.allAll fulfill (or any reject)Parallel independent requests
Promise.allSettledAll settleBatch ops where partial failure is OK
Promise.raceFirst settlesTimeouts, fastest mirror
Promise.anyFirst fulfillsFallback chains

Timeout Pattern

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}
 
const data = await withTimeout(fetch('/api/slow'), 5000);

Concurrency Control

Promise.all fires everything at once. In production, you often need to limit concurrency:

async function mapWithConcurrency(items, fn, concurrency = 5) {
  const results = [];
  const executing = new Set();
 
  for (const [i, item] of items.entries()) {
    const p = fn(item).then((result) => {
      executing.delete(p);
      return result;
    });
    executing.add(p);
    results[i] = p;
 
    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }
 
  return Promise.all(results);
}

Error Handling Strategy

In production codebases, async error handling is architectural, not ad-hoc:

async function safeAsync(fn) {
  try {
    const result = await fn();
    return [result, null];
  } catch (error) {
    return [null, error];
  }
}
 
const [user, error] = await safeAsync(() => fetchUser(1));
if (error) {
  // handle gracefully
}

Retry with Exponential Backoff

async function retry(fn, { attempts = 3, baseDelay = 1000 } = {}) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === attempts - 1) throw error;
      const delay = baseDelay * 2 ** i + Math.random() * 100;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

Interview Signal

Interviewers at the senior level look for:

  1. Concurrency awareness — knowing when Promise.all vs sequential matters, and the foot-guns of concurrent awaits
  2. Error boundaries — not just try/catch, but structured error propagation strategies
  3. Production patterns — retry, timeout, cancellation (AbortController), and concurrency limiting
  4. Mental model — understanding the event loop, microtask queue, and why async functions always return promises