← Back to Course Overview

Module 4: Consuming Data From the Network

In this module, you'll learn how to work with APIs and handle asynchronous data. Web applications frequently need to retrieve and send data to servers to provide dynamic content and functionality. You'll understand the fetch API, promises, HTTP methods, and how to handle success and error cases in network requests to create robust, data-driven web applications.

JavaScript Promises

Learn about promises and how they help handle asynchronous operations. Promises are objects representing the eventual completion or failure of an asynchronous operation, allowing you to chain operations and handle results when they become available, rather than blocking execution while waiting for responses.

Understanding Asynchronous Code

In JavaScript, we have the concept of 'asynchronous' code. This means code that does not run instantly in line. Instead, perhaps the code needs to wait a moment, wait for something to happen, or wait until data comes back from a server.

Using asynchronous code can be challenging at first because we need to recognize which code will be asynchronous and which will run instantly. Take the following example, which will run instantly and which will wait to run?

setTimeout( () => {
    console.log('Hello!');
}, 1000);

console.log('Over here!');

Even if you have never seen setTimeout before, you probably realized that it will wait a moment to run, where console.log will run instantly. If you run this code in your console you should see Over here! print first because setTimeout will wait 1 second (1000 ms) to run.

Asynchronous code is everywhere in JavaScript. It is an important concept to understand.

Introduction to Promises

We have a few different ways to approach asynchronous code; in the last example, we saw callbacks (which you already understand). Other concepts are async/await and Promises.

Promises are a design pattern for use when handling asynchronous code in JavaScript. We use them as an alternative to nesting multiple callbacks. You may have heard of a problem called callback hell. Promises are a way to avoid this problem.

A promise is a response from the object that it will let us know when it has completed (or errored) what we have asked it to do. A promise can exist in one of three states:

  • Pending: a state where the promise is neither rejected nor fulfilled (this is the state it is in when we first call it).
  • Fulfilled: a state where all's well and a resolved value can be used by our code.
  • Rejected: a state where something went wrong and an error will need to be dealt with.

If the promise succeeds, it will return the value as a parameter into a callback passed into .then(). If the promise fails, the callback passed into the .catch() runs, taking an error as its argument.

Using Functions That Return Promises

Learn how to work with functions that return promises and handle their results. Many modern JavaScript APIs, including fetch, return promises. You'll learn to use .then() to handle successful responses, chain operations in a readable sequence, and understand promise states (pending, fulfilled, rejected).

Working with Promises

When working with promises, we use the .then() method to handle the resolved value and chain operations. Here's an example of creating and using a promise:

let time = 0;
const timeMachine = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve((time += 1000));
    }, 1000);
  });
};

timeMachine()
  .then(newTime => {
    console.log(newTime); // OUTPUTS: 1000
  });

We can chain multiple .then() calls to create a sequence of operations:

timeMachine()
  .then(newTime => {
    const myTime = newTime / 1000;
    return `${myTime} seconds have passed`;
  })
  .then(newString => {
    console.log(newString); // OUTPUTS "1 seconds have passed"
  });

This chaining is particularly powerful when dealing with functions that return promises, such as the fetch API:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log('Data received:', data);
    return data;
  })
  .then(data => {
    // Do something else with the data
    return processData(data);
  })
  .then(processedData => {
    // Continue the chain with the processed data
    console.log('Processed data:', processedData);
  });

Handling Failure with .catch

Learn how to handle errors in promises using the catch method. Network requests can fail for many reasons (server errors, network issues, invalid data), and proper error handling is crucial for a good user experience. You'll learn how to implement error handling in promise chains and display appropriate feedback to users.

Error Handling in Promises

When working with asynchronous operations, things can go wrong. Promises provide a standardized way to handle errors using the .catch() method, which catches any errors that occur in the promise chain.

Example of a promise that might reject:

const parseTime = ms => {
  return new Promise((resolve, reject) => {
    const timeString = time / 1000;
    if (ms > 999) {
      resolve(`${timeString} seconds have passed`);
    } else {
      reject(new Error(`ms is less than 1 second promise rejected!`));
    }
  });
};

Using catch to handle errors:

timeMachine()
  .then(parseTime)
  .then(timePassed => {
    console.log(timePassed); // Only runs if the promise is fulfilled
  })
  .catch(err => {
    console.log(err); // OUTPUT: [Error: ms is less than 1 second promise rejected!]
  });

When using the Fetch API, error handling is particularly important:

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      // Creating an error and throwing it will trigger the catch block
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
    // Process the data
  })
  .catch(error => {
    console.error('Error fetching data:', error);
    // Show user-friendly error message
    document.querySelector('.error-message').textContent = 
      'We couldn\'t load the data. Please try again later.';
  });

A good practice is to have error handling for both specific parts of your promise chain and a general catch at the end to prevent unhandled promise rejections.

Using HTTP Methods

Learn about different HTTP methods and how to use them with the fetch API. HTTP methods like GET, POST, PUT, and DELETE determine what action you're performing when communicating with a server. Each method has specific use cases: retrieving data (GET), submitting data (POST), updating existing data (PUT), or removing data (DELETE).

Common HTTP Methods

HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the web. When making requests to a server, we use different HTTP methods to indicate the desired action:

  • GET: Retrieve data from the server (read-only)
  • POST: Send data to the server to create a new resource
  • PUT: Update an existing resource on the server
  • DELETE: Remove a resource from the server
  • PATCH: Partially update an existing resource

Using the Fetch API with different HTTP methods:

GET Request (Default)

fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(users => console.log(users))
  .catch(error => console.error('Error:', error));

POST Request

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com'
  })
})
  .then(response => response.json())
  .then(newUser => console.log('User created:', newUser))
  .catch(error => console.error('Error:', error));

PUT Request

fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'John Smith',
    email: 'john.smith@example.com'
  })
})
  .then(response => response.json())
  .then(updatedUser => console.log('User updated:', updatedUser))
  .catch(error => console.error('Error:', error));

DELETE Request

fetch('https://api.example.com/users/123', {
  method: 'DELETE'
})
  .then(response => {
    if (response.ok) {
      console.log('User deleted successfully');
    } else {
      throw new Error('Failed to delete user');
    }
  })
  .catch(error => console.error('Error:', error));

Hitting an API with Postman

Learn how to test APIs using Postman before implementing them in your code. Postman is a popular tool for API development that allows you to send requests to endpoints, view responses, and test different parameters without writing code. This helps you understand the API's structure and behavior before integrating it into your application.

Testing APIs with Postman

Before implementing API calls in your code, it's often helpful to test them using a tool like Postman. This allows you to:

  • Verify that the API works as expected
  • Understand the structure of request and response data
  • Test different parameters and edge cases
  • Debug issues without the complexity of your application code

When testing an API in Postman:

  1. Enter the endpoint URL (e.g., https://api.example.com/users)
  2. Select the HTTP method (GET, POST, PUT, DELETE, etc.)
  3. Add any required headers (e.g., Content-Type, Authorization)
  4. For POST/PUT requests, add the request body in the appropriate format
  5. Click "Send" to make the request and view the response

Once you've tested your API calls in Postman, you can implement them in your JavaScript code using Fetch or Axios with confidence that you understand how the API works and what to expect in the responses.

From Postman to Code

Postman can even generate code snippets for you. After creating a working request in Postman:

  1. Click the "Code" button (usually found under the Save button)
  2. Select JavaScript and your preferred library (Fetch, Axios, etc.)
  3. Copy the generated code into your application
  4. Adapt the code to fit your application's structure and error handling

This workflow helps ensure that your API integration works correctly and helps you understand the API requirements before writing code.

Additional Resources