Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • How-Tos
    • How To Code at Marcy: Code Style Guide
    • How to Do Short Response and Coding Assignments
    • How to Debug
    • How to PEDAC
    • How to Create A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
Powered by GitBook
On this page
  • Intro: Promise Returning Is Tricky
  • The benefits of async/await
  • Fetching "Synchronously" with Async/Await
  • Handling Errors with Try/Catch
  1. Mod 3 - Async & APIs

4. Async & Await

Previous3. Building a Fetching AppNext5. A Generic Fetch Handler

Last updated 4 months ago

Follow along with code examples !

Intro: Promise Returning Is Tricky

What's wrong with this code? Why does it print undefined?

fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
  .then((response) => {
    if (!response.ok) {
      throw Error(`Fetch failed. ${response.status} ${response.statusText}`);
    }
    const readingPromise = response.json();
  })
  .then((data) => {
    console.log(data); // prints undefined
  })
  .catch((error) => {
    console.log("Error caught!");
    console.log(error.message);
  })
Answer

We forgot to return readingPromise from the first .then callback, so the second .then callback never got a value!

The benefits of async/await

Using the async/await syntax with try and catch has a number of benefits. The main ones being readability and debuggability.

  • We can write async code in a synchronous-like manner

  • We avoid having to write a bunch of callbacks

  • We can avoid common mistakes made when using callbacks

  • try/catch is a more general-purpose way of handling errors that can be used for more than just fetching.

Fetching "Synchronously" with Async/Await

When fetching, it is important to observe that we have two sequential asynchronous processes involved:

  1. fetching

  2. reading the response body's ReadableStream.

And we've handled those processes using Promises like this:

  • fetch() is an asynchronous function so it returns a promise.

  • The first .then callback handles that promise's response value.

  • In that callback we start reading in the response data with response.json(), which returns another promise.

  • The second .then callback handles that promise's responseData value.

// in fetch-helpers.js
export const getUsers = () => {
  return fetch('https://reqres.in/api/users')
    .then((response) => {
      if (!response.ok) {
        throw Error(`Fetch failed. ${response.status} ${response.statusText}`);
      }
      // start the async process of reading the ReadableStream and return a promise
      return response.json();
    })
    .then((responseData) => {
      // do something with the response data
      console.log("Here is your data:", responseData);
      return responseData;
    })
    .catch((error) => {
      console.error(error.message);
    });
}

// in main.js
const main = () => {
  getUsers().then(renderUsers);
}

Effectively, we are saying, "wait for the fetch() promise to resolve, then wait for the response.json() promise to resolve, then begin working with the data".

However, working with callbacks can be conceptually difficult to follow at times. So, an alternate syntax was created to achieve the same effect but without the need for callbacks.

This approach utilizes the async and await keywords:

  • The await keyword is placed before any function call that returns a Promise

    • It causes our code to pause and wait for the Promise to resolve.

    • It then "unpacks" the Promise and returns the resolved value.

  • The async keyword is placed before any function that uses await inside:

    • Any value returned by this function now will be wrapped in a Promise

// A function marked with `async` returns a Promise
// You MUST put `async` if you want to use `await`
const getUsersAsyncAwait = async () => { 
  // When we await a Promise, we are given the resolved value (the Response object)
  // An awaited statement becomes "blocking"
  const response = await fetch('https://reqres.in/api/users');
  
  if (!response.ok) {
    throw Error(`Fetch failed. ${response.status} ${response.statusText}`);
  }

  // Since response.json() also returns a Promise, we can await it too.
  const jsonData = await response.json();

  console.log("here is the data: ", data);

  // we can also just return await response.json()
  return jsonData;
};

// in main.js
const main = async () => {
  const users = await getUsersAsyncAwait();
  renderUsers(users);

  // Or, we can still use .then if we want to
  getUsersAsyncAwait().then(renderUsers);
}

Handling Errors with Try/Catch

There are some functions (like fetch() and response.json()) that are capable of throwing an error.

We can manually throw our own errors too using throw new Error('message'). When an error is thrown, the program crashes immediately:

console.log('start');

throw new Error('uh oh!');

console.log('end'); // this code won't even run

try/catch is the standard way to handle those errors and it will prevent the application from crashing. Instead, we can handle any errors that occur and continue on:

console.log('start');

try {
  throw Error('uh oh!')
}
catch (error) {
  console.error(error.message)
}

console.log('end'); // now, this code can run!

So, when using fetch() or response.json(), we should always use try and catch, allowing us to gracefully handle errors:

const getUsersAsyncAwait = async () => { 
  try {
    const response = await fetch('https://reqres.in/api/users');
  
    if (!response.ok) {
      throw Error(`Fetch failed. ${response.status} ${response.statusText}`);
    }
    // Since response.json() also returns a Promise, we can await it too.
    const users = await response.json();
    console.log("here is the data: ", users);
    
    return users;
  } catch (error) {
    console.log("Error caught! " + error.message);
    return null;
  }
};
here
Intro: Promise Returning Is Tricky
The benefits of async/await
Fetching "Synchronously" with Async/Await
Handling Errors with Try/Catch