Timeout implementation of fetch
Personally, I think the flag is not the best idea, or at least not, in the scenario you have added it, as it looks like a global variable.

The scenario you have set up now, poses a few problems as you present it, nl:

setTimeout(() =&gt; {
  if (!responseReturned) {
     timedOut = true;
     spanSubmitErr.style.display = "inline";
  }
}, 8000);


This doesn't interact with the request at all, at most, the user will see something on his screen, however the response will still be running, and your code may eventually react on it, so you are pretty much unsure if it really worked. 

But don't forget that any callback you create should be canceled in the end (through setTimeout (see PatrickRoberts comment below) or setInterval), so that there is no need for you to check if a response returned ;). I personally still prefer the setInterval and clearInterval methods to do that.

Suggested rewrite ES5



function fetch() {
  return new Promise( function (resolve) {
    var timeout = (Math.random() * 10 + 1) * 1000;
    console.log('Waiting ' + timeout + ' ms');
    setTimeout( function() {
      resolve();
    }, timeout);
  });
}

function _fetch( options ) {
  if (!options) {
    options = {
      timeout: 5000
    };
  }
  return new Promise( function(resolve, reject) {
    var interval = setInterval( function() {
      clearInterval( interval );
      reject( { statusCode: 504, status: 'timeout - client' } );
    }, options.timeout || 5000 );
    fetch().then( function( response ) {
      clearInterval( interval );
      resolve( response );
    } ).catch( function( error ) {
      clearInterval( interval );
      reject( error );
    } );
  });
}

function test() {
  var internal = function q() {
    _fetch().then( function() { 
      setTimeout( function() { 
        q(); 
      }, 0 ); 
    }).catch( function( result ) {
      console.log( result );
    });
  };
  internal();
}

test();




Suggested rewrite ES6

If your code base is not to large, or if you are willing to put the time in for refactoring, I would suggest you wrap fetch with your own function, like in the following code sample.

You know that fetch will return a Promise, so create your own version of fetch, which you could then export if necessary, and redirect your files that point to the fetch functionality to your own implementation.



// random method that waits certain time, then resolves
const fetch = () =&gt; {
  let timeout = (Math.random() * 10 + 1) * 1000;
  console.log(`waiting ${timeout}`);
  return new Promise((resolve) =&gt; {
    setTimeout( () =&gt; resolve(), timeout);  
  });
}

const _fetch = ( opt = { timeout: 5000 } ) =&gt; {
  return new Promise( (resolve, reject) =&gt; {
    console.time('interval');
    let interval = setInterval(() =&gt; {
      console.timeEnd('interval');
      clearInterval( interval );
      reject({ message: 'timeout occured', statusCode: 504, status: 'timeout - client' });
    }, opt.timeout );
    fetch().then( (...args) =&gt; {
      console.timeEnd('interval');
      clearInterval( interval );
      resolve(...args);
    }).catch( (...args) =&gt; {
      console.timeEnd('interval');
      clearInterval( interval );
      reject(...args);
    });
  } );
}

async function runRandomTest() {
  let ok = 1;
  while (ok) {
    await _fetch().catch( (...args) =&gt; {
      console.log(...args);
      ok = false;
      });
  }
  console.log('test completed');
}

runRandomTest();




Explanation of rewrite

The biggest change would be here, that the fetch function gets wrapped, and called from _fetch, which you would then use inside your code

If you look at it, it works slightly different, nl:

function _fetch( options ) {
  if (!options) {
    options = {
      timeout: 5000
    };
  }
  return new Promise( function(resolve, reject) {
    var interval = setInterval( function() {
      clearInterval( interval );
      reject( { statusCode: 504, status: 'timeout - client' } );
    }, options.timeout || 5000 );
    fetch().then( function( response ) {
      clearInterval( interval );
      resolve( response );
    } ).catch( function( error ) {
      clearInterval( interval );
      reject( error );
    } );
  });
}


It returns it's own promise, and uses the setInterval. In case the result is returned (or another error is thrown), the clearInterval is called and the promise gets handled by either resolve or reject. In case the interval occurs, the promise gets rejected, and the clearInterval is also called.

Note that if you run the test (at least the es6 one), you can see the time it took before the timeout occured, and it will not run longer. Neither does the then method of the fetch still execute. It is true however that your httprequest will still be open, but I don't think that should be such a huge problem, as it will not interact with your own code anymore.

It is still important though to handle the catch of the fetch and pipe it through, so that the interval gets cleared, but that you also get notified of the other errors that might have occured during the request.