Search code examples
javascriptasynchronousfile-uploadxmlhttprequesttimeout

Javascript: Reduce timeout value for asynchronous xhr file upload after file has been completely sent?


Usually I always find a solution to my questions on stackoverflow, you guys are really great! But after hours and hours of searching I am still stuck with this problem:

I am programming a browser extension for Chrome/FF which uploads a file to a server via xhr post method. If the server is not responding I want to give the user a feedback that the connection timed out. Therefore first I used a timeout value of 10000 ms because I thought that the timeout only starts counting after the request/file has been completely sent. However, it turned out that on slow connections the upload took more than 10 sec and the timeout event was fired before the file was sent completely. For some larger files the upload took even some minutes and hence I had to set the timeout value up to 5 min. This is however unpractical on fast connections. On fast connections I do not want to wait 5 min before I can inform the user that the connection timed out if the server was not responding.

Hence I am looking for a xhr solution which should:

  1. first sets the timeout to e.g. 5 min during the file upload in order to allow the file to be sent completely also on slow connections
  2. sets the timeout to e.g. 10 sec after the file was completely sent in order to timeout the connection if the server does not respond within 10 sec after it has received the whole file.

Is there any possibility to achieve this?

Edit: What I tried so far was:

[...]
var originalTimeout = 10000;
xhr.timeout = originalTimeout;
[...]

xhr.upload.addEventListener('loadstart', function(e) {
    xhr.timeout = 600000;
});
xhr.upload.addEventListener('loadend', function(e) {
    xhr.timeout = originalTimeout;
});

I did several upload tests and like this obviously the timeout value was changed to 600000 by the upload-'loadstart' event listener because sending the file took usually much more then 10 sec and no timeout event was fired. But thereafter always a timeout event was fired immediately after the file was completely sent to the server. I was able to confirm on the server side that the file was received completely and also the the response was sent. However, the response was not received by the script as the timeout event was fired before.

Any idea would be really appreciated!


Solution

  • You can use a Promise, specifically the Promise.race method, along with the XMLHttpRequestUpload object. You can have one promise be a timer, and the other waiting for the XMLHttpRequest:

    var xhr = new XMLHttpRequest();
    xhr.timeout = (1000 * 60) * 5 //5 minute initial timeout timer.
    
    xhr.ontimeout = function() {
      doSomething(); //if 5 minute timeout is done.
    };
    
    var xhrUpload = xhr.upload;
    xhrUpload.loadend = function() { //when upload is done, move on to 10s server timer.
    raceThis(xhr, callback)
    }
    
    xhr.open('METHOD', url, true);
    
    function raceThis(xmlReq, callback) {
      var waitForServer = new Promise(function(resolve, reject) {
        xmlReq.onreadystatechange = function() {
          if(xmlReq.readyState = 2) { 
            resolve(false); //the moment HTTP headers are sent as a response, resolve
          }
        }
      })
      var waitFor10Seconds = new Promise(function(resolve, reject) {
        window.setTimeOut(function() {
          resolve(true) 
        }, 10000)
      })
    
      Promise.race([waitForServer(), waitFor10Seconds()])
      .then(function(serverTimedOut) {
        if(serverTimedOut) {
          //do stuff if the server timed out 
        } else {
          //do stuff if the server didnt time out
        };
    
        callback(serverTimedOut) //you can even use dedicated callbacks! 
      })
    }
    

    This is a crude representation, there are many ways to expand on this but this is the main idea.