Lesson 22 of 30

Async/Await

Modern Asynchronous JavaScript

Async/await is modern syntax that makes asynchronous code look and behave more like synchronous code, improving readability.

The 'async' keyword makes a function return a Promise, while 'await' pauses function execution until a Promise is resolved.

This approach eliminates promise chaining and makes error handling with try/catch more natural and familiar.

Example
// Traditional Promise syntax
function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      console.log(data);
      return data;
    })
    .catch(error => {
      console.error('Error:', error);
    });
}

// Modern async/await syntax
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}

// Async function always returns a Promise
async function greet(name) {
  return `Hello, ${name}!`;
}

greet('Alice').then(message => console.log(message));

// Multiple async operations in parallel
async function fetchMultiple() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetch('/api/users').then(r => r.json()),
      fetch('/api/posts').then(r => r.json()),
      fetch('/api/comments').then(r => r.json())
    ]);

    return { users, posts, comments };
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}
  • async keyword - Declares an async function that returns a Promise
  • await keyword - Pauses execution until Promise resolves
  • try/catch - Handle errors in async functions naturally
  • Sequential operations - Multiple awaits run one after another
  • Parallel operations - Use Promise.all() with await for concurrent execution
  • Return values - Async functions automatically wrap returns in Promise
  • Top-level await - Can use await at module level in modern JS
Try Async/Await
HTML
<div class="async-demo">
  <h3>Async/Await Examples</h3>

  <div class="example">
    <h4>1. Sequential Operations</h4>
    <button id="sequentialBtn">Run Sequential</button>
    <div id="sequentialOutput"></div>
  </div>

  <div class="example">
    <h4>2. Parallel Operations</h4>
    <button id="parallelBtn">Run Parallel</button>
    <div id="parallelOutput"></div>
  </div>

  <div class="example">
    <h4>3. Error Handling</h4>
    <button id="successAsyncBtn">Success</button>
    <button id="errorAsyncBtn">Error</button>
    <div id="errorOutput"></div>
  </div>
</div>
CSS
.async-demo {
  padding: 20px;
  max-width: 600px;
}

.example {
  margin: 20px 0;
  padding: 15px;
  background: #f5f5f5;
  border-radius: 8px;
}

h4 {
  margin-top: 0;
  color: #333;
}

button {
  padding: 10px 15px;
  margin: 5px;
  background: #2196F3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

button:hover {
  background: #1976D2;
}

#sequentialOutput, #parallelOutput, #errorOutput {
  margin-top: 10px;
  padding: 10px;
  background: white;
  border: 2px solid #ddd;
  border-radius: 4px;
  min-height: 50px;
  font-family: monospace;
  font-size: 13px;
}

.timing {
  color: #666;
  font-style: italic;
}

.success {
  border-color: #4CAF50 !important;
  background-color: #e8f5e9 !important;
}

.error {
  border-color: #f44336 !important;
  background-color: #ffebee !important;
  color: #c62828;
}
JavaScript
// Helper function to simulate async operations
function delay(ms, value) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

// Example 1: Sequential Operations
document.getElementById('sequentialBtn').addEventListener('click', async function() {
  const output = document.getElementById('sequentialOutput');
  output.innerHTML = 'Starting sequential operations...<br>';

  const startTime = Date.now();

  try {
    output.innerHTML += 'Step 1: Fetching user data (1s)...<br>';
    const user = await delay(1000, { name: 'Alice', id: 1 });
    output.innerHTML += `✓ Got user: ${user.name}<br>`;

    output.innerHTML += 'Step 2: Fetching user posts (1s)...<br>';
    const posts = await delay(1000, ['Post 1', 'Post 2']);
    output.innerHTML += `✓ Got ${posts.length} posts<br>`;

    output.innerHTML += 'Step 3: Fetching comments (1s)...<br>';
    const comments = await delay(1000, ['Comment 1', 'Comment 2', 'Comment 3']);
    output.innerHTML += `✓ Got ${comments.length} comments<br>`;

    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    output.innerHTML += `<br><span class="timing">Total time: ${elapsed}s (sequential)</span>`;
  } catch (error) {
    output.innerHTML += `Error: ${error}<br>`;
  }
});

// Example 2: Parallel Operations
document.getElementById('parallelBtn').addEventListener('click', async function() {
  const output = document.getElementById('parallelOutput');
  output.innerHTML = 'Starting parallel operations...<br>';

  const startTime = Date.now();

  try {
    output.innerHTML += 'Fetching all data simultaneously...<br>';

    const [user, posts, comments] = await Promise.all([
      delay(1000, { name: 'Bob', id: 2 }),
      delay(1000, ['Post 1', 'Post 2', 'Post 3']),
      delay(1000, ['Comment 1', 'Comment 2'])
    ]);

    output.innerHTML += `✓ Got user: ${user.name}<br>`;
    output.innerHTML += `✓ Got ${posts.length} posts<br>`;
    output.innerHTML += `✓ Got ${comments.length} comments<br>`;

    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    output.innerHTML += `<br><span class="timing">Total time: ${elapsed}s (parallel - much faster!)</span>`;
  } catch (error) {
    output.innerHTML += `Error: ${error}<br>`;
  }
});

// Example 3: Error Handling
async function fetchWithError(shouldFail) {
  await delay(500);
  if (shouldFail) {
    throw new Error('Network error occurred!');
  }
  return { data: 'Success data', status: 200 };
}

document.getElementById('successAsyncBtn').addEventListener('click', async function() {
  const output = document.getElementById('errorOutput');
  output.innerHTML = 'Fetching data...<br>';

  try {
    const result = await fetchWithError(false);
    output.innerHTML += `✓ Success: ${JSON.stringify(result)}`;
    output.className = 'success';
  } catch (error) {
    output.innerHTML += `✗ Error: ${error.message}`;
    output.className = 'error';
  }
});

document.getElementById('errorAsyncBtn').addEventListener('click', async function() {
  const output = document.getElementById('errorOutput');
  output.innerHTML = 'Fetching data...<br>';

  try {
    const result = await fetchWithError(true);
    output.innerHTML += `✓ Success: ${JSON.stringify(result)}`;
    output.className = 'success';
  } catch (error) {
    output.innerHTML += `✗ Error caught: ${error.message}`;
    output.className = 'error';
  }
});
Notes
  • await only in async: The await keyword can only be used inside async functions (or at top level in modules).
  • Error Handling: Use try/catch blocks to handle errors from awaited promises. Uncaught errors will reject the async function.
  • Performance: Use Promise.all() with await when operations can run in parallel - it's much faster than sequential awaits.