How do you exchange setTimeout in a promise

I am trying to run a test suite for an object that returns a promise. I want to associate several actions with short timeouts between them. I thought that “then” the call that returned the promise would wait for the promise to be fulfilled before starting the next chained call.

I created a function

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

to try to wrap setTimeout in Promise.

Then in my test case I call 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 call of xh.fire , and the second on the call of xu.fire , and would expect a two-second gap when a continues from the first breakpoint to the second.

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

What am I doing wrong?

+10
source share
5 answers

TL DR - you correctly wrapped setTimeout in a promise, the problem is that you are using it incorrectly

 .then(promiseTimeout(2000)).then 

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

The promise method then takes two arguments:

Promise. then (completed, rejected)

Both onFulfilled and onRejected are optional arguments:

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

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

You do not pass a function then

Consider how you do it:

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

against how this should be done:

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

First exits 'hello' immediately

The second displays 2000 in 2 seconds

Therefore you should do:

 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(); }); }); 

As an alternative:

 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

A lot has changed over the years - the arrow marking makes it even easier.

First, I would have defined the promise 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 resolved by time (delay duration). Thinking about it, I don’t understand why it would be very useful, rather I would:

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

The above will result in the result of the previous promise (much more useful)

But this is a function that returns a function, so the rest of the ORIGINAL code can be left unchanged. The essence of the source code, however, is that you don’t need to pass values ​​along the .then chain, which is even easier

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

and the original code in question it can now be used without changes

 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(); }); }); 
+14
source

To make a timeout that works the way you want, write a function that takes a delay and returns a function suitable for switching to then .

 function timeout(ms) { return () => new Promise(resolve => setTimeout(resolve, ms)); } 

Use it as follows:

 Promise.resolve() . then(timeout(1000)) . then(() => console.log("got here");); 

However, most likely, you will want to access the allowed value of the promise, leading to a timeout. In this case, for the function created by timeout() to go through the value:

 function timeout(ms) { return value => new Promise(resolve => setTimeout(() => resolve(value), ms)); } 

Use it as follows:

 Promise.resolve(42) . then(timeout(1000)) . then(value => console.log(value)); 
+4
source
 await new Promise((resolve, reject)=>{ // wait for 50 ms. setTimeout(function(){resolve()}, 50); }); console.log("This will appear after waiting for 50 ms"); 

This can be used in an asynchronous function, and execution will wait until the specified interval.

+2
source

This is, as mentioned above, but I feel that this can be easily done with:

 const setTimeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms)) 

The setTimeoutProise function takes a timeout in ms and passes it to the setTimeout function. After the timeout, the resolution method passed to the promise is executed.

Which can be used like this:

 setTimeoutPromise(3000).then(doSomething) 
+1
source

Another approach for adding delays to Promise without having to pre-define or import an auxiliary function (which I personally like the most) is to extend the property of the Promise constructor:

 Promise.prototype.delay = function (ms) { return new Promise(resolve => { window.setTimeout(this.then.bind(this, resolve), ms); }); } 

I reject callback as it always resolve .

Demo

 Promise.prototype.delay = function(ms) { console.log('[log] Fetching data in ${ms / 1000} second(s)...'); return new Promise(resolve => { window.setTimeout(this.then.bind(this, resolve), ms); }); } document.getElementById('fetch').onclick = function() { const duration = 1000 * document.querySelector('#duration[type="number"]').value; // Promise.resolve() returns a Promise // and this simply simulates some long-running background processes // so we can add some delays on it Promise .resolve('Some data from server.') .delay(duration) .then(console.log); } 
 <div> <input id="duration" type="number" value="3" /> <button id="fetch">Fetch data from server</button> </div> 

Or, if you need it to also be .catch() -able, that's when you need a second argument ( reject ). Note that catch() processing will also occur after a delay:

 Promise.prototype.delay = function(ms) { console.log('[log] Fetching data in ${ms / 1000} second(s)...'); return new Promise((resolve, reject) => { window.setTimeout(() => { this.then(resolve).catch(reject); }, ms); }); } document.getElementById('fetch').onclick = function() { const duration = 1000 * document.querySelector('#duration[type="number"]').value; Promise .reject('Some data from server.') .delay(duration) .then(console.log) .catch(console.log); // Promise rejection or failures will always end up here } 
 <div> <input id="duration" type="number" value="3" /> <button id="fetch">Fetch data from server</button> </div> 
0
source

Source: https://habr.com/ru/post/1236496/


All Articles