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
  • The Fetch Helper
  • Why Are We Checking the Content Type?
  • A Useful Improvement
  • Why return a tuple?
  1. Mod 3 - Async & APIs

5. A Generic Fetch Handler

Previous4. Async & AwaitNextMod 4 - Project Week!

Last updated 4 months ago

Follow along with code examples !

The Fetch Helper

The code for fetching data is almost always the same:

  • In a try block, fetch from a URL and parse the response as JSON

  • In a catch block, log the caught error. Any error that occurs in the try block will be caught by this one shared catch block

const fetchData = async (url, options = {}) => {
  try {
    // pass along the options to fetch
    const response = await fetch(url, options);

    // Throw an error if the response was not 2xx - let the catch statement handle it
    if (!response.ok) throw new Error(`Fetch failed. ${response.status} ${response.statusText}`)

    // Guard clause: make sure that the content type of the response is JSON before reading it
    const contentType = response.headers.get('content-type');
    if (contentType === null || !contentType.includes('application/json')) {
      // If the contentType of the response is not JSON, read the stream as plain text
      const textData = await response.text();
      return textData;
    }

    // Otherwise read the stream as JSON
    const jsonData = await response.json();
    return jsonData;
  }
  catch (error) {
    // if there was an error, log it and return the error
    console.error(error.message);
    return error;
  }
}

The function below does the following:

  • It accepts a url and an options argument allowing other types of requests to be made (POST, PATCH/PUT, DELETE, etc...). If the caller of fetchData does not provide options, it will default to an empty object.

  • If the !response.ok guard clause is triggered, an error is thrown instead of returning. This let's us handle 4xx and 5xx responses in the catch block and treat them the same as errors thrown by fetch and response.json().

  • It checks the content type of the response to determine how to parse (with response.json() or response.text())

Why Are We Checking the Content Type?

We've added in one detail that can occasionally trip us up: the data is NOT in JSON format. DELETE requests, for example, often do not return ANY data in response. In other cases, we may be fetching other data formats. In those cases, we can't use the response.json function and instead will use response.text to read the incoming response body ReadableStream.

So, to make our function more flexible, we check the response.headers to determine the contentType and then use response.json() if we're dealing with JSON, and response.text() if we're dealing with anything else.

A Useful Improvement

One subtle change will make the function more easier to use.

const fetchData = async (url, options = {}) => {
  try {
    const response = await fetch(url, options);

    if (!response.ok) throw new Error(`Fetch failed. ${response.status} ${response.statusText}`)

    const contentType = response.headers.get('content-type');
    if (contentType === null || !contentType.includes('application/json')) {
      const textData = await response.text();
    // Return a "tuple", making error handling on the "receiving" end easier
      return [textData, null]
    }

    // Return a "tuple", making error handling on the "receiving" end easier
    const jsonData = await response.json();
    return [jsonData, null]
  }
  catch (error) {
    // if there was an error, log it and return a tuple: [data, error]
    console.error(error.message);
    return [null, error];
  }
}

This version returns the data in a "tuple" format — an array with 2 values where the first value is always the data (if present) and the second value is always the error (if present). Only one of the two values will ever be present.

Why return a tuple?

You may be wondering, why couldn't we write this helper function such that it just returns the data if there are no errors, or returns the error if there is one?

The reason we don't do this is to make the code that uses this function cleaner. The code that uses fetchData will need to know if the data it receives is an error or JSON data.

{
    "message": "https://images.dog.ceo/breeds/mix/dog3.jpg",
    "status": "success"
}

Since error objects and jsonData objects can look so similar in their structure, we can't simply check the structure of the returned object to know if it was an error or JSON data. We could do something like this:

const getDogImage = async () => {
  const dogImage = await fetchData('https://dog.ceo/api/breeds/image/random')
  console.log(dogImage.message); // I can't know if this is going to be an error message or a dog picture.

  // I could check the type of the object
  if (dogImage instanceof Error) {
    // handle the error
  }
  else {
    // use the dog image
  }
}

But if our fetchData function always returns a [data, error] tuple where one of those values will ALWAYS be null while the other is defined, then the code that uses fetchData will become much cleaner:

const getDogImage = async () => {
  // either dogImage or error will be null
  const [dogImage, error] = await fetchData('https://dog.ceo/api/breeds/image/random')
  
  if (error) {
    // if the error exists at all, that means that dogImage is null and we should handle the error
    // We can render a message to the user that something went wrong without crashing the program
  }
  else {
    // otherwise, the error must be null and we can render the dogImage
  }
}

The problem is that error objects and the jsonData can often be difficult to differentiate. An error object will have a message property, and often times, so do JSON response objects! Take the as an example. It will return its data like this:

Dog Image API
here
The Fetch Helper
Why Are We Checking the Content Type?
A Useful Improvement
Why return a tuple?