This question already has answers here:
What is the JavaScript version of sleep()?
(91 answers)
Closed 1 year ago.
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?
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();
});
});
To make a timeout which works as you want, write a function which takes a delay, and returns a function suitable for passing to then.
function timeout(ms) {
return () => new Promise(resolve => setTimeout(resolve, ms));
}
Use it like this:
Promise.resolve() . then(timeout(1000)) . then(() => console.log("got here"););
However, it is likely that you will want to access the resolved value of the promise leading into the timeout. In that case, arrange for the function created by timeout() to pass through the value:
function timeout(ms) {
return value => new Promise(resolve => setTimeout(() => resolve(value), ms));
}
Use it like this:
Promise.resolve(42) . then(timeout(1000)) . then(value => console.log(value));
This has already been answered above, but I feel this could be done easily with:
const setTimeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms))
setTimeoutProise function accept wait time in ms and passes it down to the setTimeout function. Once the wait time is over, the resolve method passed down to the promise is executed.
Which could be used like this:
setTimeoutPromise(3000).then(doSomething)
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 async function and the execution will wait till given interval.
Another approach for adding delays to Promises without having to predefine or import a helper function (that I personally like best) 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'm leaving out the reject callback since this is meant to 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, here is when you need the second argument (reject). Note that the catch() handling will also occur after the 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>
Related
The following code
function doSomething(msg){
return new Promise((resolve, reject) => {
setTimeout(
() => {
console.log(msg);
resolve();
},
2000);
})
}
let start = Date.now();
let end;
doSomething("1st Call")
.then(()=>doSomething("2nd Call"))
.then(()=>doSomething("3rd Call"))
.then(()=>{
end = Date.now();
console.log('start',start);
console.log('end',end);
console.log('elapsed time',end-start);
})
prints 1st Call, 2nd and 3rd, with 2 seconds between each console.log statement, as expected
However, if I remove the arrow functions from the then blocks, the behaviour is totally different, i.e
doSomething("1st Call")
.then(doSomething("2nd Call"))
.then(doSomething("3rd Call"))
.then(()=>{
end = Date.now();
console.log('start',start);
console.log('end',end);
console.log('elapsed time',end-start);
})
With this code, all the console.log statements get printed at the same time, and the elapsed time is just 2 seconds, as opposed to 2 seconds per function (6 seconds total as in the first example)
In other words, for the code to work correctly, the then function needs to take a function (the arrow one in this case), and from inside that function, then I can do further function calls.
Why can't I just pass the function directly, why does it need to be nested in another function?
.then() expects a callback function which will be called when the Promise on which .then() method is called - fulfills.
When you do this
.then(doSomething("2nd Call"))
instead of registering a callback function which will be invoked at some later time, doSomething() is called immediately.
You could pass a reference to a function to the .then() method and .then() will call that function when Promise fulfills.
.then(doSomething)
but you won't be able to pass any argument to doSomething function.
.then(doSomething("2nd Call")) will only work if doSomething returns a function. In that case, returned function will be registered as a callback and will be called when Promise fulfills.
You could use .bind() to get a function which will be used as a callback to .then() method
function doSomething(msg){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(msg);
resolve();
}, 2000);
});
}
let start = Date.now();
let end;
doSomething("1st Call")
.then(doSomething.bind(null, '2nd Call'))
.then(doSomething.bind(null, '3rd Call'))
.then(()=>{
end = Date.now();
console.log('start',start);
console.log('end',end);
console.log('elapsed time',end-start);
})
If you use then(fn()), it will not return a value for the next .then(...) to use, so it can continue immediately.
When you use .then(variable => fn()), the next .then(...) will wait for the first to resolve or reject.
It's similar to the difference between the functions
function a () {
stuff
}
function b (){
return stuff
}
1.
let f = Promise.resolve(15);
console.log(f);
f.then(v => console.log(v));
Result:
[object Promise] { ... }
15
Everything works perfectly as thought ... but when I plug it inside a setTimeout() function I am not able to reason why the result is 1 ...
2.
let f = Promise.resolve(setTimeout(() => Promise.resolve(15), 1000));
console.log(f);
f.then(v => console.log(v));
Result:
[object Promise] { ... }
1
Thanks!
setTimeout does nothing with the return value of the callback function you pass it. Putting a resolved promise there is entirely irrelevant.
The resolved value of the outer promise is the return value of setTimeout.
The returned timeoutID is a positive integer value which identifies
the timer created by the call to setTimeout(); this value can be
passed to clearTimeout() to cancel the timeout.
— MDN
If you want to resolve a promise after some time then use a normal promise constructor.
const promise = new Promise(
(res) => {
const resolve = () => res(15);
setTimeout(resolve, 1000);
}
);
promise.then( value => console.log(value) );
What you're seeing returned is the setTimeout's timeoutID, a number assigned by the browser to track all the separate setTimeout calls.
The mistake you're making is here:
let f = Promise.resolve(setTimeout(() => Promise.resolve(15), 1000));
You're creating a Promise that instantly resolves using Promise.resolve and making it instantly resolve with setTimeout(() => Promise.resolve(15), 1000). This is probably the first timeout within the script, which is why the timeoutID is 1.
You want the promise to resolve with 15, not setTimeout[...]. What you should do to achieve the result you're aiming for is this:
let f = new Promise(resolve => setTimeout(() => resolve(15), 1000));
That is, create a new Promise that starts a new setTimeout which runs after 1000ms and resolves the containing Promise.
I am trying to wrap my head around the incredibly confusing topics of Promises in Javascript.
One of the doubts that I encountered is that what happens when a then() callback actually returns a promise.
Look at the code below taken from javascript.info
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
//Actually resolves before calling the next then()
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
Anyone of the inner promises is actually fulfilled before moving on to the next then statement.
Why is that? How does Javascript know when the returned value is a promise itself?
Is there really a check within the JS interpreter for this? Like something like this-:
if(ret_value instanceof Promise) {
wait_for_promise(ret_value);
}
This does not really make sense intuitively. You would think that the return value will be kept as is, ie the next then() in the chain would receive a promise and not the result of the promise.
I come from a Java background which is probably why this loose typing is irritating me.
The actual check that is performed is against thenables, not only against Promises.
If the returned value of a Promise resolver has a .then method, this method will be called, and the next resolver/rejecter will receive this method's value as argument:
const thenable = { // simple object, not an instance of Promise per se
then: (res, rej) => setTimeout(() => res('from thenable'), 1000)
};
Promise.resolve(1)
.then(v => thenable)
.then(console.log)
Promise.resolve()
.then(() => {
return new Promise(res => setTimeout(() => res(1),1000))
})
.then(v => {
console.log(v); //1
});
Take a look at the second then in the example above: I passed in a callback to log the value. Because a Promise was returned in the first then, JavaScript will not execute the callback in the second then until the Promise returned from the first then is resolved.
If, instead, in the first then I passed back a value (below), instead of a Promise, JavaScript would execute the callback I passed into the second then immediately.
Promise.resolve()
.then(() => {
return 1
})
.then(v => {
console.log(v); //1
});
Why is that? How does Javascript know when the returned value is a promise itself?
Maybe this can illustrate an answer to your question:
let x = Promise.resolve();
console.log(x instanceof Promise); //true - JavaScript "knows" that x is a promise
let y = 1;
console.log(y instanceof Promise); //false
In my opinion #kaiido s answer is the one You're searching for.
Simplified, an implementation of a promise internally could do a check like this one:
if( typeof(retValue.then)==="function" ) {
retValue.then( yourCallback );
}else{
yourCallback( retValue );
}
Wrote this on mobile phone. Please excuse my formatting.
I need to somehow loop over the work array passed to _start then
for each of the items in the array, I need to somehow call the corresponding function with the same name.
I don't have control over the number of items in work the array or the number of items, I do know that there will always be a corresponding function.
I don't want to call all the functions at the same time, once the first function resolves after 3 seconds, I then want to call the second function, once the second function resolves after 3 seconds I then want to call the third function. Once the third function resolves after another 3 seconds I want to call _done().
In this example each function takes 3 seconds to complete _done wont gete called for 9 seconds.
function _start(data){
// Insert some kinda native magic loop
}
function _one(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(1);
}, 3000);
})
};
function _two(){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(2);
}, 3000);
})
};
function _done(){
console.log('All done in 9 seconds)
}
(function(){
var work = ['_one', '_two', '_two'];
_start(work);
})();
Given the order is dicated by the array, you can use reduce to aggregate the promises into a chain
const _start = (...actions) => {
return actions.reduce((chain, action) => {
const func = this[action];
return chain.then(() => func());
}, Promise.resolve());
}
...
_start('_one', '_two', '_three').then(() => console.log('All done'));
See it in action - the example appends an extra then to the chain just to output any results from the promises (probably outwith the scope of this question but something you may have to consider if getting data back is required).
Update
Can see you intend on invoking _start from a different context in which the functions are declared, this is fine but you need to make sure you set the correct context before hand i.e.
const self = this;
(function() {
_start.bind(self)('_one', '_two', '_two');
})();
A function which creates a promise which sleeps:
const sleep = n => () => new Promise(resolve => setTimeout(resolve, n));
A function which sleeps after some input promise:
const sleepAfter = n => p => p.then(sleep(n));
A function which chains a bunch of promises, represented by functions:
const chain = (...promises) => promises.reduce((ret, promise) => ret.then(promise),
Promise.resolve());
Run a bunch of functions yielding promises, sleeping in between:
const _start = promises => chain(promises.map(sleepAfter(3000)));
Now just:
_start(_one, _two, _three).then(_done);
Try using this:
_one().then((firstResponse) {
return_two();
}) .then((secondResponse) => {
*second and first respone are already done*
});
Use promises then
_one().then((responseOne) => {
return _two();
}).then((responseTwo) => {
// _one & _two are done
});
So my understanding of promises lead me to believe that my other promises would run one after another in my then chain but I'm doing something wrong here.
The code I'm using currently is as follows
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code.... //this is omitted code
);
const getData = () => Promise.resolve(
someObj.getProducts('data.json')
);
const updateProduct = () => Promise.resolve(
setTimeout(()=>{
someObj.updateProductHTML()
}, 0)
);
const updateDom = () => {
setTimeout(()=>{
someObj.updateDOM()
}, 0)
};
and my promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(updateDom)
;
They seem to be initially running in order but the Ajax call I have in getProducts also has a for loop worker to build my array of objects and is finishing after all my .thens run.
I am attempting to at least have my data call and worker finish before updateProduct and updateDOM runs
--- UPDATE ---
ok so with the revised promises set up as such as per suggestions in the comments and Samanime's answer
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
);
const getData = () => new Promise( resolve => {
console.log('getData');
someObj.getProducts('data.json');
resolve();
}
);
const updateProduct = () => new Promise( resolve =>{
console.log('updateProduct');
someObj.updateProductHTML();
resolve();
});
//execute promise chain
mainPromise()
.then(getData)
.then(updateProduct)
.then(page.updateDOM)
;
I updated the promises to not immediately resolve and am attempting to call resolve after I call my functions ( although I'm uneasy as to if resolve will be called before or after these functions ).
Unfortunately, I'm still getting the same behavior. I've added console logs to my functions as well as my promises and I'm getting this list back
log.spinner spinning
log.getData
log.updateProduct
log.A log from the function updateProduct calls
log.48 product objects created (for loop worker in my getProducts function)
log.Data retrieved and assigned
the last two logs would ideally be called after getData
None of the calls or functions outside of the ones provided are return promises, I'm working on legacy code and I'm moving away from the setTimeout trick as well as my results weren't consistent.
--UPDATE 2 --
The problem I'm having is known as Forking/Splitting. I just need to figure out chaining specifically to fix my issue.
-- FINAL --
this is what I ended up working out
// execute promise chain
mainPromise()
.then(getData);
//the timeout is a little hack to ensure the sequence is kept
mainPromise()
.then(() => {
setTimeout(() => {
myObj.updateProductHTML();
myObj.updateDOM();
}, 0);
});
apparently .then(foo).then(bar) just runs foo and bar at the same time
seems to be working ok right but I feel like somethings not right with it still.
I believe it's because Promise.resolve() doesn't do quite what you think it does.
Promise.resolve() creates a new Promise and resolves it immediately using the value of what it's given. Things like setTimeout return their id (an integer) immediately, so they aren't doing what you want. Your getProducts() is probably an async call, so it may be returning null or something as well (if it's returning a Promise or returns the value synchronously, then it's fine).
You're better off writing a normal Promise and calling resolve() at the appropriate time.
const mainPromise = () => Promise.resolve(
console.log('spinner spinning...')
...spinner code....
);
// Assuming it's already returning a Promise or synchronous response. If it isn't, then deal with it like the setTimeout ones below.
const getData = () => someObj.getProducts('data.json')
const updateProduct = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateProductHTML();
resolve();
}, 0)
});
// You don't NEED to in your example since it's at the end of the chain, but you probably want to wrap this too in case you add to the chain.
const updateDom = () => new Promise(resolve => {
setTimeout(()=>{
someObj.updateDOM();
resolve();
}, 0)
});