What is the best general practice to timeout a function in promise [closed]

Promisify a function call with timeouts

I have seen many resources provide similar examples of using Promise.race to timeout a function call within a given period of time. This is a very good example of how Promise.race can be used in practice. Here's some sample code:

function doWithinInterval(func, timeout) {
    var promiseTimeout = new Promise(function (fulfill, reject) {
       // Rejects as soon as the timeout kicks in
       setTimeout(reject, timeout);
    });
    var promiseFunc = new Promise(function (fulfill, reject) {
        var result = func(); // Function that may take long to finish
        // Fulfills when the given function finishes
        fulfill(result);
    });

    return Promise.race([promiseTimeout, promiseFunc]);
}

The simple approach above using Promise.race rejects the promise as soon as the timeout kicks in before func has completed. Otherwise, the project is fulfilled once the func function finishes before the timeout interval.

This sounds good and easy to use.

However, is this the best practice to use timeout in Promise?

Surely, the approach above can be employed if we want to set a timeout against a function call using Promises. The operations still appear to be a good promise. However, is this considered a good practice of using timeout in a Promise? If not, what is the disadvantage of using this?

I've look for alternative approaches, but couldn't find a native Promise way to do this.

Instead, some external Promise libraries offer timeout functionality as follows:

  • Bluebird supplies .timeout()

  • WinJS supplies .timeout() as well

  • Q also comes with .timeout().

However, Promise.timeout() doesn't appear to be part of the standard ECMAScript 6 API (please correct me if I'm wrong). Is there any recommended way to handle timeouts natively with ES6 Promises?


Solution 1:

It depends on what you mean by timeout.

If you expect the function to stop, then no.

If you just want to stop waiting for it, then yes (quick to whip up in ES6):

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var timeout = (p, ms) => Promise.race([p, wait(ms).then(() => {
    throw new Error("Timeout after " + ms + " ms");
})]);

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var timeout = (p, ms) => Promise.race([p, wait(ms).then(() => {
  throw new Error("Timeout after " + ms + " ms");
})]);

// Example:

var log = msg => div.innerHTML += "<p>" + msg + "</p>";
var failed = e => log(e.toString() + ", line " + e.lineNumber);

log("Waiting 5 seconds...");
timeout(wait(5000), 2000)
.then(() => log("...Done."))
.catch(failed);
<div id="div"></div>

If you want to cancel the operation (make it stop), then hopefully that operation comes with an API to cancel it, and you should use that, since an ES6 promise is not a control surface.

Cancelable promises is a controversial topic in ES6, but some of the libraries mentioned do offer the concept.

Solution 2:

The native Promise.race method doesn't clear the timer of the timeout promise after the actual promise completes thus the process will wait until the timeout promise is also complete. This means that if you set the timeout to 1h and our promise is completed after 1min then the process will wait for 59min before it exits.

Use this method instead:

export function race({promise, timeout, error}) {
  let timer = null;

  return Promise.race([
    new Promise((resolve, reject) => {
      timer = setTimeout(reject, timeout, error);
      return timer;
    }),
    promise.then((value) => {
      clearTimeout(timer);
      return value;
    })
  ]);
}