← Back to Blog
JavaScript 📅 12 Mar 2026 ⏱ 15 min read
⚙️

JavaScript Async Patterns: Promises, async/await, and Beyond

From callbacks to async generators — parallel execution, AbortController, robust error handling, and production async code patterns.

Why Async Mastery Matters

Async programming is the foundation of every JavaScript application. Network requests, timers, user interactions, and database queries all operate asynchronously. Writing clean, correct async code is one of the most valuable skills in the JavaScript toolkit.

The Callback Problem

getUser(id, function(err, user) {
  if (err) { handleError(err); return; }
  getPosts(user.id, function(err, posts) {
    if (err) { handleError(err); return; }
    getComments(posts[0].id, function(err, comments) {
      if (err) { handleError(err); return; }
      renderPage(user, posts, comments);
    });
  });
}); // callback hell — repeated error handling, infinite nesting

Promises

getUser(id)
  .then(user    => getPosts(user.id))
  .then(posts   => getComments(posts[0].id))
  .then(comments => renderPage(comments))
  .catch(err    => handleError(err)); // one catch for all

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async/await

async function loadDashboard(userId) {
  try {
    const user  = await getUser(userId);
    const posts = await getPosts(user.id);
    renderPage(user, posts);
  } catch (error) {
    handleError(error);
  } finally {
    hideSpinner(); // always runs
  }
}

Parallel Execution — The Critical Optimisation

// Sequential: 600ms — each waits for the previous
const user     = await getUser(id);     // 200ms
const settings = await getSettings(id); // 200ms
const posts    = await getPosts(id);    // 200ms

// Parallel: ~200ms — all run simultaneously
const [user, settings, posts] = await Promise.all([
  getUser(id), getSettings(id), getPosts(id)
]);

Promise Combinators

// allSettled: wait for all, never rejects
const results = await Promise.allSettled([fetch(a), fetch(b)]);
results.forEach(r =>
  r.status === 'fulfilled' ? process(r.value) : log(r.reason)
);

// any: first SUCCESS wins
const data = await Promise.any([loadFromCache(k), fetchFromAPI(url)]);

AbortController — Request Cancellation

class SearchService {
  constructor() { this.controller = null; }
  async search(query) {
    if (this.controller) this.controller.abort();
    this.controller = new AbortController();
    try {
      const res = await fetch(
        '/api/search?q=' + encodeURIComponent(query),
        { signal: this.controller.signal }
      );
      return await res.json();
    } catch (e) {
      if (e.name === 'AbortError') return null; // expected
      throw e;
    }
  }
}

Async Generators for Pagination

async function* fetchAllPages(url) {
  let nextUrl = url;
  while (nextUrl) {
    const res  = await fetch(nextUrl);
    const data = await res.json();
    yield data.items;
    nextUrl = data.nextPageUrl || null;
  }
}
for await (const items of fetchAllPages('/api/products')) {
  appendToUI(items);
}

Parallel execution with Promise.all, cancellation with AbortController, and streaming with async generators — these three patterns separate senior JavaScript developers from intermediate ones.

Share: 𝕏 Twitter in LinkedIn
← Previous
Building Accessible Web Components: The Complete WCAG Guide
Next →
The Complete Guide to Web Typography in 2026