Skip to Content
Lab 2

Promises and Error Handling Challenge

Scenario

You are developing an e-commerce dashboard that fetches data from various APIs, including a product catalog, user reviews, and a sales report. Each API call could potentially fail due to network issues, incorrect endpoints, or data inconsistencies. To build a stable and user-friendly application, you need to manage these scenarios with proper error handling.

Task

In this lab, you will build a small application that leverages asynchronous API calls using Promises. You will also implement error handling to ensure that the application can manage unexpected issues gracefully. This lab will test your ability to work with asynchronous programming concepts, as well as to manage errors effectively in a real-world scenario.

Objectives

By the end of this lab, you will be able to:

  • Apply Promises to manage multiple asynchronous operations in JavaScript.
  • Implement chained Promises to handle sequential data retrieval and manage dependencies between API calls.
  • Utilize .catch() and .finally() to handle errors and perform cleanup tasks in a Promise chain.
  • Design custom error classes to improve error identification and debugging.
  • Implement a retry mechanism to manage failed asynchronous requests, enhancing application resilience.
  • Analyze the benefits and challenges of using error handling strategies in complex asynchronous workflows.

Instructions

Part 1: Set Up Your Project

  1. Create a New Project:

    • Set up a new project folder, initialize it with npm init -y, and install any necessary dependencies, such as TypeScript.
  2. Create an apiSimulator.ts file:

    • This file will contain functions that simulate API requests using Promises.
    • Each function should return a Promise that resolves with mock data after a delay, or rejects with an error message.

Part 2: Implement API Simulation Functions

Simulate Asynchronous API Calls:

Create the following functions in apiSimulator.ts, ensuring each returns a Promise:

  • fetchProductCatalog(): Simulates fetching a list of products, each with id, name, and price.
    • Resolve the Promise with an array of mock products after a 1-second delay.
    • Use Math.random() to sometimes reject the Promise with an error message, e.g., "Failed to fetch product catalog".

Example Code for fetchProductCatalog()

export const fetchProductCatalog = (): Promise<{ id: number; name: string; price: number }[]> => { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.8) { resolve([ { id: 1, name: "Laptop", price: 1200 }, { id: 2, name: "Headphones", price: 200 }, ]); } else { reject("Failed to fetch product catalog"); } }, 1000); }); };

Continue with fetchProductReviews() and fetchSalesReport() functions similarly, adding realistic mock data and a chance for rejection.

  • fetchProductReviews(productId: number): Simulates fetching reviews for a product.

    • Resolve the Promise with an array of reviews after a 1.5-second delay.
    • Reject the Promise randomly with an error message, e.g., "Failed to fetch reviews for product ID ${productId}".
  • fetchSalesReport(): Simulates fetching a sales report with totalSales, unitsSold, and averagePrice.

    • Resolve the Promise with a mock sales report after a 1-second delay.
    • Reject randomly with an error message, e.g., "Failed to fetch sales report".

Part 3: Build the Main Application Logic

  1. Create an index.ts file to contain the main logic of your application.

  2. Write a Function to Handle API Calls and Display Data:

    • Use fetchProductCatalog() to fetch product details and display them.
    • For each product, fetch the reviews using fetchProductReviews(productId).
    • After fetching products and reviews, retrieve the sales report using fetchSalesReport().
  3. Implement Error Handling Using Promises:

    • Use .catch() to handle any errors from fetchProductCatalog(), fetchProductReviews(), and fetchSalesReport().
    • Display error messages to the console if any of the calls fail.
    • Use .finally() to log a message indicating that all API calls have been attempted.

Part 4: Custom Error Classes

  1. Create Custom Error Classes for different error scenarios:
    • NetworkError for network-related issues.
    • DataError for data-related issues (e.g., missing fields in the API response).
  2. Update API Simulation Functions to use these custom error classes when rejecting Promises.

Part 5: Optional Challenge

  1. Create a Retry Mechanism:

    • Write a utility function retryPromise that accepts an async function, the number of retry attempts, and the delay between attempts.
      • Hint: Use setTimeout to delay the next attempt.
      • Hint: You will need to utilize recursion to implement this function. Not sure what recursion is, or don’t quite remember? This is an opportunity to practice your research abilities or review!
    • Use this function to retry API calls that fail initially.
  2. Implement retryPromise with API Calls to retry up to three times for each API call before giving up.


Critical Thinking Questions

  • Why is it important to handle errors for each individual API call rather than just at the end of the promise chain?
  • How does using custom error classes improve debugging and error identification?
  • When might a retry mechanism be more effective than an immediate failure response?