JavaScript and TypeScript course Β· Module 4: Asynchronous JavaScript

Fetch API

15 min read
In this lesson18

In the modern Jurassic Park, in addition to local monitoring and control systems, we also need to communicate with external systems and APIs. Whether checking weather forecasts for the park, synchronizing data with global dinosaur databases, or integrating with visitor booking systems - all of this requires a solid method for making HTTP requests. In this exercise we will learn about the Fetch API, the modern interface for making network requests in JavaScript.

What Is the Fetch API?

The Fetch API is a modern, built-in JavaScript interface for making HTTP requests. It replaces older approaches such as XMLHttpRequest, offering a more ergonomic and powerful Promise-based toolset.

Main advantages of the Fetch API:

  • Uses Promises, making it easier to handle asynchronous operations
  • Has a simpler and more intuitive syntax than XMLHttpRequest
  • Offers better control over HTTP requests
  • Allows easy chaining of asynchronous operations
  • Is a browser standard and available in Node.js (from version 18)

Basic Fetch Usage

The basic Fetch syntax is very simple:

1fetch(url)
2  .then(response => response.json())
3  .then(data => console.log(data))
4  .catch(error => console.error('Error:', error));

Let's see how we could use Fetch in our Jurassic Park:

1// Fetching dinosaur data from the API
2function fetchDinosaurData(id) {
3  return fetch(`https://api.jurassic-park.com/dinosaurs/${id}`)
4    .then(response => {
5      // Check that the response is successful
6      if (!response.ok) {
7        throw new Error(`HTTP Error: ${response.status}`);
8      }
9      return response.json();
10    })
11    .then(data => {
12      console.log(`Fetched dinosaur data: ${data.name}`);
13      return data;
14    })
15    .catch(error => {
16      console.error(`Failed to fetch dinosaur #${id} data:`, error);
17      throw error;
18    });
19}
20
21// Using the function
22fetchDinosaurData(42)
23  .then(dino => {
24    console.log(`Dinosaur #42: ${dino.name}, Species: ${dino.species}`);
25    updateInfoPanel(dino);
26  })
27  .catch(error => {
28    displayErrorMessage(`Failed to fetch dinosaur info: ${error.message}`);
29  });

Making Different Types of HTTP Requests

Fetch performs a GET request by default, but we can easily specify a different request type and send data:

1// Updating dinosaur data with a PUT request
2async function updateDinosaurData(id, newData) {
3  try {
4    const response = await fetch(`https://api.jurassic-park.com/dinosaurs/${id}`, {
5      method: 'PUT',
6      headers: {
7        'Content-Type': 'application/json',
8        'Authorization': `Bearer ${apiToken}`
9      },
10      body: JSON.stringify(newData)
11    });
12
13    if (!response.ok) {
14      throw new Error(`Update error: ${response.status}`);
15    }
16
17    const updatedDinosaur = await response.json();
18    console.log(`Updated dinosaur #${id} data`);
19    return updatedDinosaur;
20  } catch (error) {
21    console.error(`Error updating dinosaur #${id}:`, error);
22    throw error;
23  }
24}
25
26// Creating a new dinosaur record with a POST request
27async function addNewDinosaur(data) {
28  try {
29    const response = await fetch('https://api.jurassic-park.com/dinosaurs', {
30      method: 'POST',
31      headers: {
32        'Content-Type': 'application/json',
33        'Authorization': `Bearer ${apiToken}`
34      },
35      body: JSON.stringify(data)
36    });
37
38    if (!response.ok) {
39      throw new Error(`Error adding dinosaur: ${response.status}`);
40    }
41
42    const newDinosaur = await response.json();
43    console.log(`Added new dinosaur #${newDinosaur.id}`);
44    return newDinosaur;
45  } catch (error) {
46    console.error('Error adding dinosaur:', error);
47    throw error;
48  }
49}
50
51// Deleting a dinosaur with a DELETE request
52async function deleteDinosaur(id) {
53  try {
54    const response = await fetch(`https://api.jurassic-park.com/dinosaurs/${id}`, {
55      method: 'DELETE',
56      headers: {
57        'Authorization': `Bearer ${apiToken}`
58      }
59    });
60
61    if (!response.ok) {
62      throw new Error(`Error deleting dinosaur: ${response.status}`);
63    }
64
65    // For DELETE the server may not return any data
66    if (response.status !== 204) { // No Content
67      const data = await response.json();
68      console.log(`Deleted dinosaur #${id}:`, data);
69      return data;
70    }
71
72    console.log(`Deleted dinosaur #${id}`);
73    return { success: true, id };
74  } catch (error) {
75    console.error(`Error deleting dinosaur #${id}:`, error);
76    throw error;
77  }
78}

The Response Object

When we call fetch(), it returns a Promise that resolves to a Response object. This object contains all information about the HTTP response:

1fetch('https://api.jurassic-park.com/status')
2  .then(response => {
3    console.log('Status:', response.status); // e.g. 200
4    console.log('Status OK:', response.ok); // true for status 200-299
5    console.log('Content type:', response.headers.get('content-type'));
6    console.log('All headers:', [...response.headers.entries()]);
7
8    return response.json(); // Parse response as JSON
9  })
10  .then(data => {
11    console.log('Data:', data);
12  });

The Response object offers various methods for processing response data:

MethodDescription
response.json()Parses content as JSON and returns a Promise with the result
response.text()Returns a Promise with content as text
response.blob()Returns a Promise with content as a Blob object (binary data)
response.formData()Parses content as FormData
response.arrayBuffer()Returns a Promise with content as ArrayBuffer

It is important to remember that you can only call one of these methods on a Response object. The response body can only be processed once - trying to call response.json() multiple times will throw an error.

Error Handling in Fetch

An important characteristic of the Fetch API is that the Promise returned by fetch() will not be rejected for HTTP errors such as 404 or 500. It will only be rejected for network problems such as no connection.

To properly handle HTTP errors, you need to check the response.ok property or the response.status code:

1fetch('https://api.jurassic-park.com/dinosaurs/999')
2  .then(response => {
3    if (!response.ok) {
4      throw new Error(`HTTP Error: ${response.status}`);
5    }
6    return response.json();
7  })
8  .then(data => {
9    console.log('Data:', data);
10  })
11  .catch(error => {
12    console.error('Error:', error.message);
13    // Here we can handle both HTTP errors and network errors
14  });

You can expand this logic to better handle different status codes:

1fetch('https://api.jurassic-park.com/dinosaurs/999')
2  .then(response => {
3    if (response.status === 404) {
4      throw new Error('Dinosaur not found');
5    } else if (response.status === 401) {
6      throw new Error('Unauthorized - please log in again');
7    } else if (response.status === 403) {
8      throw new Error('No permission for this operation');
9    } else if (!response.ok) {
10      throw new Error(`HTTP Error: ${response.status}`);
11    }
12    return response.json();
13  })
14  .then(/* ... */)
15  .catch(/* ... */);

Fetch Configuration Options

Fetch accepts an optional second argument that allows customizing the request:

1fetch(url, {
2  method: 'POST',           // HTTP method (GET, POST, PUT, DELETE, etc.)
3  headers: {                // HTTP headers
4    'Content-Type': 'application/json',
5    'Authorization': 'Bearer token123'
6  },
7  body: JSON.stringify(data), // Request body (for POST, PUT, etc.)
8  mode: 'cors',             // CORS mode
9  credentials: 'include',   // Whether to include cookies
10  cache: 'no-cache',        // Cache handling strategy
11  redirect: 'follow',       // How to handle redirects
12  referrer: 'no-referrer',  // Referrer header
13  signal: abortController.signal // For cancelling the request
14})

Example with Most Options

1async function makeComplexRequest() {
2  const abortController = new AbortController();
3  const signal = abortController.signal;
4
5  // Set a timeout for the request
6  const timeout = setTimeout(() => {
7    abortController.abort();
8  }, 5000); // 5 seconds
9
10  try {
11    const response = await fetch('https://api.jurassic-park.com/data', {
12      method: 'POST',
13      headers: {
14        'Content-Type': 'application/json',
15        'Authorization': `Bearer ${apiToken}`,
16        'X-Jurassic-Version': '1.2'
17      },
18      body: JSON.stringify({
19        query: 'velociraptor',
20        limit: 10,
21        withDetails: true
22      }),
23      mode: 'cors',
24      credentials: 'include', // Include cookies
25      cache: 'no-cache',      // Do not use cache
26      redirect: 'follow',     // Automatically follow redirects
27      referrer: 'no-referrer',
28      signal                  // For cancelling the request
29    });
30
31    clearTimeout(timeout);
32
33    if (!response.ok) {
34      throw new Error(`HTTP Error: ${response.status}`);
35    }
36
37    return await response.json();
38  } catch (error) {
39    clearTimeout(timeout);
40
41    if (error.name === 'AbortError') {
42      console.error('Request aborted after timeout');
43      throw new Error('Request timed out');
44    }
45
46    console.error('Request error:', error);
47    throw error;
48  }
49}

Cancelling Requests with AbortController

AbortController is an interface that allows cancelling Fetch requests. It is especially useful for implementing timeouts or when the user wants to cancel a long-running request:

1function fetchLargeDinosaurData(query) {
2  // Create a cancellation controller
3  const controller = new AbortController();
4  const { signal } = controller;
5
6  // Set timeout
7  const timeoutId = setTimeout(() => {
8    controller.abort();
9  }, 10000); // 10 seconds
10
11  // Cancel button for the user
12  const cancelButton = document.getElementById('cancel-button');
13  cancelButton.addEventListener('click', () => {
14    controller.abort();
15    clearTimeout(timeoutId);
16    cancelButton.disabled = true;
17    cancelButton.textContent = 'Cancelled';
18  });
19
20  // Make the request with the abort signal
21  return fetch(`https://api.jurassic-park.com/search?q=${query}`, { signal })
22    .then(response => {
23      clearTimeout(timeoutId);
24      if (!response.ok) {
25        throw new Error(`HTTP Error: ${response.status}`);
26      }
27      return response.json();
28    })
29    .then(data => {
30      cancelButton.disabled = true;
31      cancelButton.textContent = 'Done';
32      return data;
33    })
34    .catch(error => {
35      clearTimeout(timeoutId);
36
37      if (error.name === 'AbortError') {
38        console.log('Request was cancelled');
39        return { cancelled: true };
40      }
41
42      throw error;
43    });
44}

Parallel Requests with Promise.all

Sometimes we need to make multiple requests at the same time:

1async function fetchPaddockData(paddockId) {
2  try {
3    const dinosaursPromise = fetch(`https://api.jurassic-park.com/paddocks/${paddockId}/dinosaurs`).then(r => r.json());
4    const statusPromise = fetch(`https://api.jurassic-park.com/paddocks/${paddockId}/status`).then(r => r.json());
5    const historyPromise = fetch(`https://api.jurassic-park.com/paddocks/${paddockId}/history`).then(r => r.json());
6
7    const [dinosaurs, status, history] = await Promise.all([
8      dinosaursPromise,
9      statusPromise,
10      historyPromise
11    ]);
12
13    return { paddockId, dinosaurs, status, history, updatedAt: new Date() };
14  } catch (error) {
15    console.error(`Error fetching data for paddock #${paddockId}:`, error);
16    throw error;
17  }
18}

Handling Different Response Formats

Fetch can handle various data formats, not just JSON:

1// Plain text
2fetch('https://api.jurassic-park.com/logs')
3  .then(response => response.text())
4  .then(text => {
5    console.log('Logs:', text);
6    document.getElementById('logs').textContent = text;
7  });
8
9// Binary data (images, files)
10fetch('https://api.jurassic-park.com/dinosaurs/42/image')
11  .then(response => response.blob())
12  .then(blob => {
13    const imageUrl = URL.createObjectURL(blob);
14    document.getElementById('dino-image').src = imageUrl;
15  });
16
17// Downloading and saving a file (in browser environment)
18fetch('https://api.jurassic-park.com/reports/monthly.pdf')
19  .then(response => response.blob())
20  .then(blob => {
21    const url = URL.createObjectURL(blob);
22    const a = document.createElement('a');
23    a.href = url;
24    a.download = 'monthly-report.pdf';
25    document.body.appendChild(a);
26    a.click();
27    document.body.removeChild(a);
28    URL.revokeObjectURL(url);
29  });

Sending Forms

Fetch can also handle form submissions:

1// Sending form data as JSON
2document.getElementById('dino-form').addEventListener('submit', async function(e) {
3  e.preventDefault();
4
5  const form = e.target;
6  const formData = new FormData(form);
7
8  // Convert FormData to a JavaScript object
9  const formObject = {};
10  for (const [key, value] of formData.entries()) {
11    formObject[key] = value;
12  }
13
14  try {
15    const response = await fetch('https://api.jurassic-park.com/dinosaurs', {
16      method: 'POST',
17      headers: {
18        'Content-Type': 'application/json'
19      },
20      body: JSON.stringify(formObject)
21    });
22
23    if (!response.ok) {
24      throw new Error(`HTTP Error: ${response.status}`);
25    }
26
27    const result = await response.json();
28    alert(`Dinosaur added successfully! ID: ${result.id}`);
29    form.reset();
30  } catch (error) {
31    console.error('Error submitting form:', error);
32    alert(`Error: ${error.message}`);
33  }
34});
35
36// Sending a form with file uploads
37document.getElementById('dino-upload-form').addEventListener('submit', async function(e) {
38  e.preventDefault();
39
40  const form = e.target;
41  const formData = new FormData(form);
42
43  try {
44    const response = await fetch('https://api.jurassic-park.com/dinosaurs/import', {
45      method: 'POST',
46      body: formData // FormData is automatically set as 'multipart/form-data'
47    });
48
49    if (!response.ok) {
50      throw new Error(`HTTP Error: ${response.status}`);
51    }
52
53    const result = await response.json();
54    alert(`Import complete! Added ${result.count} dinosaurs.`);
55  } catch (error) {
56    console.error('Error during import:', error);
57    alert(`Error: ${error.message}`);
58  }
59});

CORS (Cross-Origin Resource Sharing)

CORS is a security mechanism that controls whether a page can make requests to resources on another domain:

1// Basic CORS request
2fetch('https://api.different-domain.com/data', {
3  mode: 'cors' // Default value
4});
5
6// Request with authentication
7fetch('https://api.different-domain.com/protected-data', {
8  mode: 'cors',
9  credentials: 'include' // Send cross-origin cookies
10});

Credential options:

  • omit: Do not send cookies (default)
  • same-origin: Send cookies only to the same domain
  • include: Send cookies to other domains too

Cache Control

Fetch lets you control how the browser uses its cache:

1// Ignore cache, always request from server
2fetch(url, { cache: 'no-store' });
3
4// Check cache first, then request if not found
5fetch(url, { cache: 'default' });
6
7// Check cache first but verify with server that the response is fresh
8fetch(url, { cache: 'no-cache' });
9
10// Use only cache, do not make a network request
11fetch(url, { cache: 'only-if-cached', mode: 'same-origin' });

Practical Patterns with Fetch API

Universal API Request Function

1async function apiRequest(endpoint, options = {}) {
2  const apiBaseUrl = 'https://api.jurassic-park.com';
3  const url = `${apiBaseUrl}${endpoint}`;
4
5  const defaultOptions = {
6    headers: {
7      'Content-Type': 'application/json',
8      'Authorization': `Bearer ${localStorage.getItem('apiToken')}`
9    },
10    mode: 'cors'
11  };
12
13  const mergedOptions = {
14    ...defaultOptions,
15    ...options,
16    headers: {
17      ...defaultOptions.headers,
18      ...(options.headers || {})
19    }
20  };
21
22  if (mergedOptions.body && typeof mergedOptions.body === 'object' && !(mergedOptions.body instanceof FormData)) {
23    mergedOptions.body = JSON.stringify(mergedOptions.body);
24  }
25
26  try {
27    const response = await fetch(url, mergedOptions);
28
29    if (response.status === 401) {
30      await refreshAuthToken();
31      return apiRequest(endpoint, options);
32    }
33
34    if (response.status === 204) {
35      return { success: true };
36    }
37
38    if (!response.ok) {
39      try {
40        const errorData = await response.json();
41        throw new Error(errorData.message || `HTTP Error: ${response.status}`);
42      } catch (e) {
43        throw new Error(`HTTP Error: ${response.status}`);
44      }
45    }
46
47    const contentType = response.headers.get('content-type');
48    if (contentType && contentType.includes('application/json')) {
49      return await response.json();
50    } else if (contentType && contentType.includes('text/')) {
51      return await response.text();
52    } else {
53      return await response.blob();
54    }
55  } catch (error) {
56    console.error(`API request error (${endpoint}):`, error);
57
58    const apiError = new Error(error.message);
59    apiError.isNetworkError = !window.navigator.onLine;
60    apiError.endpoint = endpoint;
61    apiError.originalError = error;
62
63    throw apiError;
64  }
65}

Simple Cache System for Requests

1const apiCache = {
2  cache: new Map(),
3  ttl: 60000, // cache time-to-live in ms (1 minute)
4
5  async get(url, options = {}) {
6    const cacheKey = `${url}|${JSON.stringify(options)}`;
7    const cachedItem = this.cache.get(cacheKey);
8
9    if (cachedItem && Date.now() < cachedItem.expiry) {
10      console.log(`Using cache for: ${url}`);
11      return cachedItem.data;
12    }
13
14    console.log(`Making request for: ${url}`);
15    const response = await fetch(url, options);
16
17    if (!response.ok) {
18      throw new Error(`HTTP Error: ${response.status}`);
19    }
20
21    const data = await response.json();
22
23    if (!options.method || options.method === 'GET') {
24      this.cache.set(cacheKey, { data, expiry: Date.now() + this.ttl });
25    }
26
27    return data;
28  },
29
30  invalidate(urlPattern) {
31    for (const key of this.cache.keys()) {
32      if (key.includes(urlPattern)) {
33        this.cache.delete(key);
34      }
35    }
36  },
37
38  clear() { this.cache.clear(); }
39};

Retry Pattern - Automatic Retries After Failure

1/**
2 * Executes a function with automatic retries
3 * @param {Function} fn - Function to execute that returns a Promise
4 * @param {Object} options - Options
5 * @param {number} options.maxRetries - Maximum number of retries
6 * @param {number} options.retryDelay - Delay before retry (ms)
7 * @param {Function} options.shouldRetry - Function determining whether to retry
8 * @param {number} options.backoffFactor - Delay multiplier (backoff)
9 * @returns {Promise} - Promise with the function result or the last error
10 */
11async function withRetry(fn, options = {}) {
12  const {
13    maxRetries = 3,
14    retryDelay = 1000,
15    shouldRetry = (error) => true,
16    backoffFactor = 2
17  } = options;
18
19  let lastError;
20
21  for (let attempt = 0; attempt <= maxRetries; attempt++) {
22    try {
23      return await fn();
24    } catch (error) {
25      lastError = error;
26
27      if (attempt === maxRetries || !shouldRetry(error)) {
28        break;
29      }
30
31      const delay = retryDelay * Math.pow(backoffFactor, attempt);
32      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
33      await new Promise(resolve => setTimeout(resolve, delay));
34    }
35  }
36
37  throw lastError;
38}
39
40// Example usage with Fetch API
41async function fetchFromUnstableAPI() {
42  return withRetry(
43    () => fetch('https://api.jurassic-park.com/unstable-endpoint')
44      .then(response => {
45        if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
46        return response.json();
47      }),
48    {
49      maxRetries: 5,
50      retryDelay: 500,
51      backoffFactor: 1.5,
52      shouldRetry: (error) => {
53        // Only retry for 5xx errors (server errors) or network errors
54        return error.message.includes('HTTP Error: 5') ||
55               error instanceof TypeError;
56      }
57    }
58  );
59}

Limitations and Compatibility of the Fetch API

The Fetch API is available in all modern browsers, and since version 18 it is also natively available in Node.js. However, there are some limitations and compatibility concerns:

  1. No request progress support - Fetch has no built-in progress tracking for upload/download. For such cases, XMLHttpRequest may be a better option.

  2. No native timeout - Timeouts must be implemented using AbortController.

  3. Does not reject Promises for HTTP errors - Even if the server returns an error code, the Fetch Promise will resolve. You must check response.ok.

In older browsers, you may need to use a polyfill such as whatwg-fetch or a library like axios that provides a consistent API across environments.

Alternatives to the Fetch API

Although the Fetch API is natively available, alternatives exist that may offer additional features:

  1. Axios - A popular library that provides:

    • Consistent API between browser and Node.js
    • Automatic JSON data transformation
    • Built-in XSRF protection
    • Request progress tracking
    • Built-in timeouts
    • Automatic retries
    • Request cancellation
  2. jQuery.ajax() - In older projects using jQuery

  3. XMLHttpRequest - An older, more verbose alternative that offers more control, e.g., over request progress

When to Use Fetch API vs Alternatives?

  • Fetch is ideal when:

    • Starting a new project
    • You want to minimize dependencies
    • You need simple HTTP requests
    • Working with modern browsers
  • Consider alternatives when:

    • You need more advanced features (progress, built-in retries)
    • You must support older browsers without polyfills
    • Working in an environment where Fetch does not work well
    • You already use a library that offers HTTP features

Fetch API Best Practices

  1. Always check response.ok

    1fetch(url).then(response => {
    2  if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
    3  return response.json();
    4});
  2. Set appropriate headers

    1fetch(url, {
    2  headers: {
    3    'Content-Type': 'application/json',
    4    'Accept': 'application/json'
    5  }
    6});
  3. Use AbortController for timeouts

    1const controller = new AbortController();
    2const timeout = setTimeout(() => controller.abort(), 5000);
    3
    4fetch(url, { signal: controller.signal })
    5  .then(response => /* ... */)
    6  .catch(error => /* ... */)
    7  .finally(() => clearTimeout(timeout));
  4. Be careful with body processing

    1// WRONG - attempting to use body multiple times
    2fetch(url)
    3  .then(response => {
    4    const text = response.text(); // This consumes the body!
    5    const json = response.json(); // Error - body already consumed!
    6    return /* ... */;
    7  });
    8
    9// CORRECT - clone response if you need to use it multiple times
    10fetch(url)
    11  .then(response => {
    12    const clone = response.clone();
    13    return response.text().then(text => {
    14      return clone.json().then(json => {
    15        return { text, json };
    16      });
    17    });
    18  });
  5. Use async/await for readability

    1async function fetchData() {
    2  try {
    3    const response = await fetch(url);
    4    if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
    5    const data = await response.json();
    6    return data;
    7  } catch (error) {
    8    console.error('Error:', error);
    9    throw error;
    10  }
    11}

Summary

The Fetch API is a powerful, built-in tool for making HTTP requests in JavaScript. Its main advantages are the Promise-based API, ease of use, and versatility. Although it has some limitations compared to more advanced libraries like Axios, for most use cases it is completely sufficient.

In Jurassic Park, where reliable communication between different systems is key to the safety of visitors and dinosaurs, the Fetch API enables reliable data exchange between monitoring systems, databases, and user interfaces.

In the next exercise we will look at how to combine the Fetch API with other asynchronous techniques to create even more advanced systems for managing our digital Jurassic Park.

Code for this lesson: index.js
1// Callback Function Builder - Callback functions
2console.log("CALLBACK FUNCTIONS - PARK EVENT SYSTEM\n");
3
4// Callback - a function passed as an argument
5function monitorDinosaur(species, callback) {
6  console.log(`Starting monitoring: ${species}`);
7
8  // Monitoring simulation (synchronous for simplicity)
9  const status = Math.random() > 0.5 ? 'SAFE' : 'ALERT';
10  const data = {
11    species: species,
12    status: status,
13    timestamp: new Date().toLocaleTimeString()
14  };
15
16  // Calling callback with results
17  callback(data);
18}
19
20// Example 1: Simple callback
21console.log("=== EXAMPLE 1: SIMPLE CALLBACK ===\n");
22
23monitorDinosaur('T-Rex', function(result) {
24  console.log(`Monitoring result:`);
25  console.log(`  Species: ${result.species}`);
26  console.log(`  Status: ${result.status}`);
27  console.log(`  Time: ${result.timestamp}`);
28});
29
30// Example 2: Arrow function as a callback
31console.log("\n=== EXAMPLE 2: ARROW FUNCTION ===\n");
32
33monitorDinosaur('Velociraptor', (result) => {
34  const emoji = result.status === 'SAFE' ? 'OK' : '!';
35  console.log(`${emoji} ${result.species}: ${result.status} (${result.timestamp})`);
36});
37
38// Example 3: Different callbacks for different situations
39console.log("\n=== EXAMPLE 3: CONDITIONAL CALLBACKS ===\n");
40
41function checkDinosaurWithCallbacks(species, onSafe, onAlert) {
42  const isSafe = Math.random() > 0.5;
43
44  if (isSafe) {
45    onSafe(species);
46  } else {
47    onAlert(species);
48  }
49}
50
51checkDinosaurWithCallbacks(
52  'Triceratops',
53  (species) => console.log(`${species} is safe`),
54  (species) => console.log(`ALERT! Problem with ${species}!`)
55);
56
57// Example 4: Callbacks with array methods
58console.log("\n=== EXAMPLE 4: ARRAY METHODS WITH CALLBACKS ===\n");
59
60const dinosaurs = [
61  { name: 'T-Rex', danger: 10 },
62  { name: 'Velociraptor', danger: 8 },
63  { name: 'Triceratops', danger: 3 },
64  { name: 'Brachiosaurus', danger: 2 }
65];
66
67console.log("forEach - iteration:");
68dinosaurs.forEach((dino) => {
69  console.log(`  - ${dino.name} (danger: ${dino.danger})`);
70});
71
72console.log("\nfilter - only dangerous (>5):");
73const dangerous = dinosaurs.filter((dino) => dino.danger > 5);
74dangerous.forEach(dino => console.log(`  ${dino.name}`));
75
76console.log("\nmap - names:");
77const names = dinosaurs.map((dino) => dino.name);
78console.log(`  ${names.join(', ')}`);
79
80console.log("\nreduce - total danger:");
81const totalDanger = dinosaurs.reduce((sum, dino) => sum + dino.danger, 0);
82console.log(`  Total: ${totalDanger}`);
83
84// Example 5: Callback hell (problem!)
85console.log("\n=== EXAMPLE 5: CALLBACK HELL ===\n");
86
87function step1(callback) {
88  console.log("Step 1: Checking the fences...");
89  callback();
90}
91
92function step2(callback) {
93  console.log("Step 2: Checking the dinosaurs...");
94  callback();
95}
96
97function step3(callback) {
98  console.log("Step 3: Final report...");
99  callback();
100}
101
102// Nested callbacks (callback hell)
103step1(() => {
104  step2(() => {
105    step3(() => {
106      console.log("All steps completed");
107    });
108  });
109});
110
111console.log("\nProblem: Callback hell - hard to read!");
112console.log("Solution: Promises or async/await (in the next lessons)");

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What does the fetch() function return in JavaScript?

  2. 2. What is AbortController used for in the context of the Fetch API?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Write an async function that fetches data from an API using fetch(), parses the JSON response, and displays a list of dinosaurs.

  • Click in order

    Arrange the elements of a Promise chain:

  • Code editor

    Write code using Promise.allSettled to check several sensors in parallel. Analyze the results - which succeeded (fulfilled) and which failed (rejected).

  • Vertical ordering

    Arrange the elements of error handling in async/await:

Useful articles