javascriptgoogle-chromefetch-apidomexceptionabortcontroller

AbortController.abort(reason), but the reason gets lost before it arrives to the fetch catch clause


I am implementing abortable fetch calls.

There are basically two reasons for aborting the fetch on my page:

In order to discriminate the two cases I was planning to use the reason parameter of the AbortController.abort method, but the .catch clause in my fetch call always receives a DOMException('The user aborted a request', ABORT_ERROR).

I have tried to provide a different DOMException as reason for the abort in case 2, but the difference is lost.

Has anyone found how to send information to the fetch .catch clause with regards to the reason to abort?


Solution

  • In the example below, I demonstrate how to determine the reason for an abortion of a fetch request. I provide inline comments for explanation. Feel free to comment if anything is unclear.

    Re-run the code snippet to see a (potentially different) random result

    'use strict';
    
    function delay (ms, value) {
      return new Promise(res => setTimeout(() => res(value), ms));
    }
    
    function getRandomInt (min = 0, max = 1) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    // Forward the AbortSignal to fetch:
    // https://docs.github.com/en/rest/repos/repos#list-public-repositories
    function fetchPublicGHRepos (signal) {
      const headers = new Headers([['accept', 'application/vnd.github+json']]);
      return fetch('https://api.github.com/repositories', {headers, signal});
    }
    
    function example () {
      const ac = new AbortController();
      const {signal} = ac;
    
      const abortWithReason = (reason) => delay(getRandomInt(1, 5))
        .then(() => {
          console.log(`Aborting ${signal.aborted ? 'again ' : ''}(reason: ${reason})`);
          ac.abort(reason);
        });
    
      // Unless GitHub invests HEAVILY into our internet infrastructure,
      // one of these promises will resolve before the fetch request
      abortWithReason('Reason A');
      abortWithReason('Reason B');
    
      fetchPublicGHRepos(signal)
        .then(res => console.log(`Fetch succeeded with status: ${res.status}`))
        .catch(ex => {
          // This is how you can determine if the exception was due to abortion
          if (signal.aborted) {
            // This is set by the promise which resolved first
            // and caused the fetch to abort
            const {reason} = signal;
            // Use it to guide your logic...
            console.log(`Fetch aborted with reason: ${reason}`);
          }
          else console.log(`Fetch failed with exception: ${ex}`);
        });
    
      delay(10).then(() => console.log(`Signal reason: ${signal.reason}`));
    }
    
    example();