Async JavaScript

Promises, async/await, callbacks, and error handling

Callbacks

Basic Callback

Simple callback function

function fetchData(callback) { setTimeout(() => { callback("Data received"); }, 1000); }

Error Handling

Callback with error handling

function fetchData(success, error) { if (Math.random() > 0.5) { success("Data"); } else { error("Failed"); } }

Callback Hell

Nested callbacks (avoid)

fetchData1((data1) => { fetchData2(data1, (data2) => { fetchData3(data2, (data3) => { console.log(data3); }); }); });

Promises

Creating Promise

Create a new promise

const promise = new Promise((resolve, reject) => { // async operation resolve("Success"); });

Promise States

Pending, Fulfilled, Rejected

const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("Done"); // Fulfilled // reject("Error"); // Rejected }, 1000); });

.then()

Handle promise resolution

promise.then( (result) => console.log(result), (error) => console.error(error) );

.catch()

Handle promise rejection

promise.catch((error) => { console.error("Error:", error); });

.finally()

Execute after promise settles

promise.finally(() => { console.log("Promise settled"); });

Promise.all()

Wait for all promises

Promise.all([promise1, promise2, promise3]) .then((results) => console.log(results));

Promise.race()

First promise to settle

Promise.race([promise1, promise2]) .then((result) => console.log(result));

Promise.allSettled()

Wait for all to settle

Promise.allSettled([promise1, promise2]) .then((results) => console.log(results));

Async/Await

async function

Declare async function

async function fetchData() { return "Data"; }

await

Wait for promise to resolve

async function getData() { const result = await fetch("/api/data"); return result.json(); }

Error Handling

Try-catch with async/await

async function getData() { try { const result = await fetch("/api/data"); return result.json(); } catch (error) { console.error("Error:", error); } }

Multiple Awaits

Sequential async operations

async function processData() { const user = await fetchUser(); const posts = await fetchPosts(user.id); const comments = await fetchComments(posts[0].id); return { user, posts, comments }; }

Parallel Awaits

Concurrent async operations

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

Fetch API

Basic GET

Simple GET request

fetch("/api/data") .then(response => response.json()) .then(data => console.log(data));

POST Request

Send data with POST

fetch("/api/data", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "John" }) });

Error Handling

Handle fetch errors

fetch("/api/data") .then(response => { if (!response.ok) throw new Error("HTTP error"); return response.json(); }) .catch(error => console.error(error));

Async/Await with Fetch

Modern fetch usage

async function fetchData() { try { const response = await fetch("/api/data"); if (!response.ok) throw new Error("HTTP error"); return await response.json(); } catch (error) { console.error("Error:", error); } }

Common Patterns

Promise-based Timeout

Create a promise that resolves after a delay

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

// Usage
async function example() {
  console.log("Start");
  await delay(2000);
  console.log("After 2 seconds");
}

// Or with value
function delayWithValue(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

Retry Mechanism

Retry failed operations with exponential backoff

async function retry(fn, maxAttempts = 3, delay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => 
        setTimeout(resolve, delay * Math.pow(2, attempt - 1))
      );
    }
  }
}

// Usage
const result = await retry(() => fetch("/api/data"));

Concurrent Requests with Limits

Process requests in batches to avoid overwhelming the server

async function processBatch(items, batchSize = 3) {
  const results = [];
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchPromises = batch.map(item => processItem(item));
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
    
    // Optional delay between batches
    if (i + batchSize < items.length) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
  
  return results;
}

async function processItem(item) {
  // Simulate async processing
  await new Promise(resolve => setTimeout(resolve, 100));
  return `Processed: ${item}`;
}

Async Queue

Process tasks sequentially with a queue

class AsyncQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }
  
  async add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.process();
    });
  }
  
  async process() {
    if (this.processing || this.queue.length === 0) return;
    
    this.processing = true;
    
    while (this.queue.length > 0) {
      const { task, resolve, reject } = this.queue.shift();
      
      try {
        const result = await task();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
    
    this.processing = false;
  }
}

// Usage
const queue = new AsyncQueue();
queue.add(() => fetch("/api/data1"));
queue.add(() => fetch("/api/data2"));

Promise Cancellation

Cancel ongoing promises using AbortController

async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    return response.json();
  } catch (error) {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      throw new Error('Request timed out');
    }
    throw error;
  }
}

// Usage
try {
  const data = await fetchWithTimeout('/api/data', 3000);
  console.log(data);
} catch (error) {
  console.error('Error:', error.message);
}

Async Event Handling

Handle async operations in event listeners

// Button click with loading state
const button = document.getElementById('submit');

button.addEventListener('click', async (e) => {
  e.preventDefault();
  
  // Disable button and show loading
  button.disabled = true;
  button.textContent = 'Loading...';
  
  try {
    const formData = new FormData(e.target.form);
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: formData
    });
    
    if (!response.ok) throw new Error('Submission failed');
    
    const result = await response.json();
    showSuccess(result.message);
    
  } catch (error) {
    showError(error.message);
  } finally {
    // Re-enable button
    button.disabled = false;
    button.textContent = 'Submit';
  }
});

Tips & Best Practices

Always handle errors in async operations with try-catch or .catch()

Use Promise.all() for parallel operations, not sequential awaits

Avoid callback hell by using promises or async/await

Use AbortController for cancelling fetch requests

Implement proper loading states and error boundaries in UI