159

This is not a realworld problem, I'm just trying to understand how promises are created.

I need to understand how to make a promise for a function that returns nothing, like setTimeout.

Suppose I have:

function async(callback){ 
    setTimeout(function(){
        callback();
    }, 5000);
}

async(function(){
    console.log('async called back');
});

How do I create a promise that async can return after the setTimeout is ready to callback()?

I supposed wrapping it would take me somewhere:

function setTimeoutReturnPromise(){

    function promise(){}

    promise.prototype.then = function() {
        console.log('timed out');
    };

    setTimeout(function(){
        return ???
    },2000);


    return promise;
}

But I can't think beyond this.

mdon88
  • 313
  • 1
  • 12
laggingreflex
  • 32,948
  • 35
  • 141
  • 196
  • Are you trying to create your *own* promise library? – T.J. Crowder Mar 28 '14 at 08:26
  • @T.J.Crowder I wasn't but I guess now that's actually what I was trying to understand. That how a library would do it – laggingreflex Mar 28 '14 at 08:28
  • @ lagging: Makes sense, I've added an example basic promise implementation to the answer. – T.J. Crowder Mar 28 '14 at 08:43
  • I think this is a very real-world problem and one I had to solve for a massive project my company was building. There were likely better ways to do it, but I essentially needed to delay a promise's resolution for the sake of our bluetooth stack. I'll post below to show what I did. – sunny-mittal Oct 15 '16 at 23:55
  • Is this what you needed? https://jsfiddle.net/sojd6nLq/5/ – Alexander Jan 18 '17 at 09:47
  • I've created a simple npm module to add promise API for setTimeout, https://github.com/zengfenfei/delay – Kevin Mar 02 '17 at 03:21
  • 3
    Just a note that in 2017 'async' is a somewhat confusing name for a function, as you might have `async function async(){...}` – mikemaccana Jun 02 '17 at 09:34
  • This question is *not* a duplicate because a proper `setTimeout` implementation (as shown in the accepted answer) does supports `setTimeout`'s different rejection. – twinlakes Jun 06 '19 at 22:19
  • Would it be beneficial for future readers to rename `async` in this question to `later` and edit the accepted answer accordingly, to avoid any confusion? – Sebastian Simon Feb 21 '22 at 09:54

8 Answers8

216

Update (2017)

Here in 2017, Promises are built into JavaScript, they were added by the ES2015 spec (polyfills are available for outdated environments like IE8-IE11). The syntax they went with uses a callback you pass into the Promise constructor (the Promise executor) which receives the functions for resolving/rejecting the promise as arguments.

First, since async now has a meaning in JavaScript (even though it's only a keyword in certain contexts), I'm going to use later as the name of the function to avoid confusion.

Basic Delay

Using native promises (or a faithful polyfill) it would look like this:

function later(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}

Note that that assumes a version of setTimeout that's compliant with the definition for browsers where setTimeout doesn't pass any arguments to the callback unless you give them after the interval (this may not be true in non-browser environments, and didn't used to be true on Firefox, but is now; it's true on Chrome and even back on IE8).

Basic Delay with Value

If you want your function to optionally pass a resolution value, on any vaguely-modern browser that allows you to give extra arguments to setTimeout after the delay and then passes those to the callback when called, you can do this (current Firefox and Chrome; IE11+, presumably Edge; not IE8 or IE9, no idea about IE10):

function later(delay, value) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay, value); // Note the order, `delay` before `value`
        /* Or for outdated browsers that don't support doing that:
        setTimeout(function() {
            resolve(value);
        }, delay);
        Or alternately:
        setTimeout(resolve.bind(null, value), delay);
        */
    });
}

If you're using ES2015+ arrow functions, that can be more concise:

function later(delay, value) {
    return new Promise(resolve => setTimeout(resolve, delay, value));
}

or even

const later = (delay, value) =>
    new Promise(resolve => setTimeout(resolve, delay, value));

Cancellable Delay with Value

If you want to make it possible to cancel the timeout, you can't just return a promise from later, because promises can't be cancelled.

But we can easily return an object with a cancel method and an accessor for the promise, and reject the promise on cancel:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

Live Example:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

const l1 = later(100, "l1");
l1.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l1 cancelled"); });

const l2 = later(200, "l2");
l2.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l2 cancelled"); });
setTimeout(() => {
  l2.cancel();
}, 150);

Original Answer from 2014

Usually you'll have a promise library (one you write yourself, or one of the several out there). That library will usually have an object that you can create and later "resolve," and that object will have a "promise" you can get from it.

Then later would tend to look something like this:

function later() {
    var p = new PromiseThingy();
    setTimeout(function() {
        p.resolve();
    }, 2000);

    return p.promise(); // Note we're not returning `p` directly
}

In a comment on the question, I asked:

Are you trying to create your own promise library?

and you said

I wasn't but I guess now that's actually what I was trying to understand. That how a library would do it

To aid that understanding, here's a very very basic example, which isn't remotely Promises-A compliant: Live Copy

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
  <script>
    (function() {

      // ==== Very basic promise implementation, not remotely Promises-A compliant, just a very basic example
      var PromiseThingy = (function() {

        // Internal - trigger a callback
        function triggerCallback(callback, promise) {
          try {
            callback(promise.resolvedValue);
          }
          catch (e) {
          }
        }

        // The internal promise constructor, we don't share this
        function Promise() {
          this.callbacks = [];
        }

        // Register a 'then' callback
        Promise.prototype.then = function(callback) {
          var thispromise = this;

          if (!this.resolved) {
            // Not resolved yet, remember the callback
            this.callbacks.push(callback);
          }
          else {
            // Resolved; trigger callback right away, but always async
            setTimeout(function() {
              triggerCallback(callback, thispromise);
            }, 0);
          }
          return this;
        };

        // Our public constructor for PromiseThingys
        function PromiseThingy() {
          this.p = new Promise();
        }

        // Resolve our underlying promise
        PromiseThingy.prototype.resolve = function(value) {
          var n;

          if (!this.p.resolved) {
            this.p.resolved = true;
            this.p.resolvedValue = value;
            for (n = 0; n < this.p.callbacks.length; ++n) {
              triggerCallback(this.p.callbacks[n], this.p);
            }
          }
        };

        // Get our underlying promise
        PromiseThingy.prototype.promise = function() {
          return this.p;
        };

        // Export public
        return PromiseThingy;
      })();

      // ==== Using it

      function later() {
        var p = new PromiseThingy();
        setTimeout(function() {
          p.resolve();
        }, 2000);

        return p.promise(); // Note we're not returning `p` directly
      }

      display("Start " + Date.now());
      later().then(function() {
        display("Done1 " + Date.now());
      }).then(function() {
        display("Done2 " + Date.now());
      });

      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        document.body.appendChild(p);
      }
    })();
  </script>
</body>
</html>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • http://modernjavascript.blogspot.com/2013/08/promisesa-understanding-by-doing.html related :) – Benjamin Gruenbaum Mar 28 '14 at 10:26
  • your answer doesn't handle cancelTimeout – Alexander Danilov Nov 29 '17 at 14:28
  • @AlexanderDanilov: Promises aren't cancellable. You could certainly write a function that returned an object with a cancel method and, separately, an accessor for the promise, then reject the promise if the cancel method were called... – T.J. Crowder Nov 29 '17 at 14:34
  • 2
    @AlexanderDanilov: I went ahead and added one. – T.J. Crowder Nov 29 '17 at 14:47
  • @T.J.Crowder What's difference between `return { get promise() { return promise } }` and `return { promise }`? The second one works too and is shorter. – Wenfang Du Feb 04 '21 at 10:47
  • 1
    @Leon - The former makes `promise` read-only, so you can't assign to `promise` on the object you get back. It's not clear to me why I wrote it as an accessor, though, rather than as an actual readonly data property. Probably laziness -- the accessor has a nice compact syntax and I can't imagine any half-decent JavaScript engine wouldn't optimize the function call away in the normal case (since `promise` is `const`). Writing it as a read-only data property would be much more verbose. :-) – T.J. Crowder Feb 04 '21 at 12:13
  • Related useful code: https://spin.atomicobject.com/2020/01/16/timeout-promises-nodejs/ – floer32 Mar 20 '21 at 22:10
11

One-liner that wraps a promise around setTimeout

await new Promise(r => setTimeout(r, ms))

Example:

async someFunction() {
  // Do something

  // Wait 2 seconds
  await new Promise(r => setTimeout(r, 2000))

  // Do something else
}
W4G1
  • 1,337
  • 1
  • 11
  • 15
8
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(cb());
    }, delay);
  });

We can pass custom 'cb fxn' like this one

CodeFinity
  • 1,142
  • 2
  • 19
  • 19
8

Since node v15, you can use timers promise API

example from the doc:

import { setTimeout } from 'timers/promises'

const res = await setTimeout(100, 'result')

console.log(res)  // Prints 'result'

It uses signals much like browser fetch to handle abort, check the doc for more :)

kigiri
  • 2,952
  • 21
  • 23
3

Implementation:

// Promisify setTimeout
const pause = (ms, cb, ...args) =>
  new Promise((resolve, reject) => {
    setTimeout(async () => {
      try {
        resolve(await cb?.(...args))
      } catch (error) {
        reject(error)
      }
    }, ms)
  })

Tests:

// Test 1
pause(1000).then(() => console.log('called'))
// Test 2
pause(1000, (a, b, c) => [a, b, c], 1, 2, 3).then(value => console.log(value))
// Test 3
pause(1000, () => {
  throw Error('foo')
}).catch(error => console.error(error))
Wenfang Du
  • 8,804
  • 9
  • 59
  • 90
1

This is not an answer to the original question. But, as an original question is not a real-world problem it should not be a problem. I tried to explain to a friend what are promises in JavaScript and the difference between promise and callback.

Code below serves as an explanation:

//very basic callback example using setTimeout
//function a is asynchronous function
//function b used as a callback
function a (callback){
    setTimeout (function(){
       console.log ('using callback:'); 
       let mockResponseData = '{"data": "something for callback"}'; 
       if (callback){
          callback (mockResponseData);
       }
    }, 2000);

} 

function b (dataJson) {
   let dataObject = JSON.parse (dataJson);
   console.log (dataObject.data);   
}

a (b);

//rewriting above code using Promise
//function c is asynchronous function
function c () {
   return new Promise(function (resolve, reject) {
     setTimeout (function(){
       console.log ('using promise:'); 
       let mockResponseData = '{"data": "something for promise"}'; 
       resolve(mockResponseData); 
    }, 2000);      
   }); 

}

c().then (b);

JsFiddle

Yuriy N.
  • 4,936
  • 2
  • 38
  • 31
0

The simplest way

(async function() {
    console.log('1');
    
    await SleepJs(3000);
    
    console.log('2');
} )();

function SleepJs(delay) {
  return new Promise(function(resolve) {
    setTimeout(resolve, delay);
  });
}
Cesar Devesa
  • 990
  • 6
  • 14
-1

If none of those solutions worked for you, please try this

const asyncTimeout = (ms) => {
    // when you make a promise you have to resolve it or reject it
    // if you are like me that didn't get promises at all read the docs
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const users = [
                { id: 1, name: 'Pablo' },
                { id: 2, name: 'Pedro' }
            ]
            resolve(users) // this returns users
        }, ms)
    })
}

(async () => {
    const obj = await asyncTimeout(3000)
    console.log(obj)
})()
Egon Stetmann.
  • 461
  • 6
  • 14