Is there a version of setTimeout that returns an ES6 promise?

19,260

Solution 1

In Browsers

First of all no - there is no built in for this. Lots of libraries that enhance ES2015 promises like bluebird whip with it.

I think the other answer conflates executing the function and a delay, it also creates timeouts that are impossible to cancel. I'd write it simply as:

function delay(ms){
    var ctr, rej, p = new Promise(function (resolve, reject) {
        ctr = setTimeout(resolve, ms);
        rej = reject;
    });
    p.cancel = function(){ clearTimeout(ctr); rej(Error("Cancelled"))};
    return p; 
}

Then you can do:

delay(1000).then(/* ... do whatever */);

Or

 doSomething().then(function(){ return delay(1000); }).then(doSomethingElse);

If we only want the basic functionality in ES2015, it's even simpler as:

let delay = ms => new Promise(r => setTimeout(r, ms));

In Node

You can use util.promisify on setTimeout to get a delay function back - meaning you don't have to use the new Promise constructor anymore.

Solution 2

Here's how I'd implement it:

function delay(duration, func) {
  var args = Array.prototype.slice.call(arguments, 2);

  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(func.apply(null, args));
    }, duration);
  });
}

(ES5-syntax intentionally chosen)

But maybe there's a common library that already does this, or a better way to do it.

Solution 3

If you need the proper cancellation of promised timeout similar to clearTimeout - returning the promise directly from setTimeout is not convenient. Especially when using with ES7 async / await in try...finally block. It is better to have separate variable for timeout manipulation. I've implemented this approach as tiny await-timeout package. It works as following:

import Timeout from 'await-timeout';

async function foo() {
  const timeout = new Timeout();
  try {
    const fetchPromise = fetch('https://example.com');
    const timerPromise = timeout.set(1000).then(() => console.log('Timeout!'));
    await Promise.race([fetchPromise, timerPromise]);
  } finally {
    timeout.clear();
  }
}

In this example timeout will definitely be cleared in case of fetch success or any error and console.log('Timeout!') will not be called.

Share:
19,260
Michael Kropat
Author by

Michael Kropat

Projects I've published that may interest you: BetterWin32Errors — a better interface to winerror.h dapper-invoice — hours invoice featuring style over substance Is Shell Command ______ Portable? — reference website jumpapp — run-or-raise application switcher for X11 desktops MlkPwgen — secure password generator (.NET + PowerShell) secure-random-password — password generator (JavaScript) sh-realpath — a portable, pure shell implementation of realpath SSLfie — generate self-signed x.509 certificates for SSL/TLS

Updated on June 02, 2022

Comments

  • Michael Kropat
    Michael Kropat almost 2 years

    Similar to this question, but rather than asking about how promises work in general, I specifically want to know:

    What is the standard/best way to wrap setTimeout in something that returns a Promise? I'm thinking something like Angular's $timeout function, but not Angular specific.

  • freakish
    freakish over 8 years
    This looks fine. It's such a short piece of code that I don't really see a point in digging through libraries to find something similar.
  • Michael Kropat
    Michael Kropat over 8 years
    Beautiful answer. I'm totally glad I asked the question now.
  • Bergi
    Bergi over 8 years
    Don't do that. You should promisify at the lowest level - setTimeout - and move the func in a promise callback, where exceptions from it will be properly caught.
  • vitaly-t
    vitaly-t over 8 years
    Problem with this example: clearTimeout should be called when resolving also.
  • Timothy Vann
    Timothy Vann over 7 years
    when I try either version of @Benjamin Gruenbaum's answer above, the code following then( executes immediately without waiting for the delay. Example delay(2000).then(console.log("then 1")).then(delay(2000).then(console.log("then 2"))); prints both "then 1" "then 2" immediately with no delay, two seconds later the program finishes.
  • Benjamin Gruenbaum
    Benjamin Gruenbaum over 7 years
    @TimothyVann then takes a function - not a promise.
  • Bergi
    Bergi over 6 years
    This makes no sense. You're only clearing the timeout after having it awaited already…
  • vitalets
    vitalets over 6 years
    Fixed. Thank you!