Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you wrap setTimeout in a promise [duplicate]

I am trying to run a test suite for an object that returns a promise. I want to chain several actions together with short timeouts between them. I thought that a "then" call which returned a promise would wait for the promise to be fulfilled before firing the next chained then call.

I created a function

function promiseTimeout (time) {   return new Promise(function(resolve,reject){     setTimeout(function(){resolve(time);},time);   }); }; 

to try and wrap setTimeout in a Promise.

Then in my test suite, I am calling something like this ...

    it('should restore state when browser back button is used',function(done){       r.domOK().then(function(){         xh.fire('akc-route-change','/user/4/profile/new');       }).then(promiseTimeout(2000)).then(function(t){         xu.fire('akc-route-change','/user/6');       }).then(promiseTimeout(10)).then(function(t){         expect(xu.params[0]).to.equal(6);         history.back();       }).then(promiseTimeout(10)).then(function(){         expect(xu.params[0]).to.equal(4);         done();       });     }); 

I can put a breakpoint on the first xh.fire call and a second one on the xu.fire call and would have expected a two second gap when a continues from the first breakpoint to the second.

Instead it reaches the second breakpoint immediately, and the value of t at that point is undefined.

What am I doing wrong?

like image 386
akc42 Avatar asked Nov 21 '15 12:11

akc42


People also ask

How do you wrap a setTimeout in a promise?

setTimeout() is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise: const awaitTimeout = delay => new Promise(resolve => setTimeout(resolve, delay)); awaitTimeout(300).

Which will execute first promise or setTimeout?

The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away. @frankies That has more to do with the way Promises are queued and resolved. The focus of my answer is the difference between setTimeout and Promise .

Does setTimeout repeat?

setTimeout allows us to run a function once after the interval of time. setInterval allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval.

Can you resolve a promise multiple times?

No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.


1 Answers

TL;DR - you've wrapped setTimeout in a promise properly, the issue is you are using it improperly

.then(promiseTimeout(2000)).then 

will not do what you expect. The "signature" for .then is then(functionResolved, functionRejected)

A promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected)

Both onFulfilled and onRejected are optional arguments:

  • If onFulfilled is not a function, it must be ignored.
  • If onRejected is not a function, it must be ignored.

source: https://promisesaplus.com/#point-21

You are not passing a function to then

Consider the way you are doing it:

Promise.resolve('hello') .then(promiseTimeout(2000)) .then(console.log.bind(console)) 

vs how it should be done:

Promise.resolve('hello').then(function() {      return promiseTimeout(2000) }).then(console.log.bind(console)) 

The first outputs 'hello' immediately

The second outputs 2000 after 2 seconds

Therefore, you should be doing:

it('should restore state when browser back button is used', function(done) {     r.domOK().then(function() {         xh.fire('akc-route-change', '/user/4/profile/new');     }).then(function() {         return promiseTimeout(2000);     }).then(function(t) {         xu.fire('akc-route-change', '/user/6');     }).then(function() {         return promiseTimeout(10);     }).then(function(t) {         expect(xu.params[0]).to.equal(6);         history.back();     }).then(function() {         return promiseTimeout(10);     }).then(function() {         expect(xu.params[0]).to.equal(4);         done();     }); }); 

Alternatively:

it('should restore state when browser back button is used', function(done) {     r.domOK().then(function() {         xh.fire('akc-route-change', '/user/4/profile/new');     }).then(promiseTimeout.bind(null, 2000)     ).then(function(t) {         xu.fire('akc-route-change', '/user/6');     }).then(promiseTimeout.bind(null, 10)     ).then(function(t) {         expect(xu.params[0]).to.equal(6);         history.back();     }).then(promiseTimeout.bind(null, 10)     ).then(function() {         expect(xu.params[0]).to.equal(4);         done();     }); }); 

EDIT: March 2019

Over the years, things have changed a lot - arrow notation makes this even easier

Firstly, I would define promiseTimeout differently

const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time, time)); 

The above returns a function that can be called to create a "promise delay" and resolves to the time (length of delay). Thinking about this, I can't see why that would be very useful, rather I'd:

const promiseTimeout = time => result => new Promise(resolve => setTimeout(resolve, time, result)); 

The above would resolve to the result of the previous promise (far more useful)

But it's a function that returns a function, so the rest of the ORIGINAL code could be left unchanged. The thing about the original code, however, is that no values are needed to be passed down the .then chain, so, even simpler

const promiseTimeout = time => () => new Promise(resolve => setTimeout(resolve, time)); 

and the original code in the question's it block can now be used unchanged

it('should restore state when browser back button is used',function(done){   r.domOK().then(function(){     xh.fire('akc-route-change','/user/4/profile/new');   }).then(promiseTimeout(2000)).then(function(){     xu.fire('akc-route-change','/user/6');   }).then(promiseTimeout(10)).then(function(){     expect(xu.params[0]).to.equal(6);     history.back();   }).then(promiseTimeout(10)).then(function(){     expect(xu.params[0]).to.equal(4);     done();   }); }); 
like image 151
Jaromanda X Avatar answered Sep 28 '22 07:09

Jaromanda X