How to make a promise from setTimeout
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);
Using setTimeout on promise chain
To keep the promise chain going, you can't use setTimeout()
the way you did because you aren't returning a promise from the .then()
handler - you're returning it from the setTimeout()
callback which does you no good.
Instead, you can make a simple little delay function like this:
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
And, then use it like this:
getLinks('links.txt').then(function(links){
let all_links = (JSON.parse(links));
globalObj=all_links;
return getLinks(globalObj["one"]+".txt");
}).then(function(topic){
writeToBody(topic);
// return a promise here that will be chained to prior promise
return delay(1000).then(function() {
return getLinks(globalObj["two"]+".txt");
});
});
Here you're returning a promise from the .then()
handler and thus it is chained appropriately.
You can also add a delay method to the Promise object and then directly use a .delay(x)
method on your promises like this:
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
Promise.prototype.delay = function(t) {
return this.then(function(v) {
return delay(t, v);
});
}
Promise.resolve("hello").delay(500).then(function(v) {
console.log(v);
});
setTimeout with Promises
You're actually almost there. All you need is to assign the value of the resolved promise to result
, instead of assigning the Promise object directly. This is done by using result = await double(<param1>, <param2>)
.
However, since JS does not yet support top-level await, you need to wrap your whole result
assignment logic in another async function, and then call it as such:
function promisify(number, increase){ return new Promise(resolve => setTimeout(() => resolve(number * 2 + increase), 100))}
async function double(number, increase) { const value = await promisify(number, increase); return value;}
async function run() { let result; result = await double(5, 0) result = await double(10, result) result = await double(20, result)
console.log(result)}
run();
JavaScript promise resolving with setTimeout
/* why does setTimeout work with this one... */
setTimeout( () => resolve( 'Job\'s done!!!' ), 3000 );
when the timeout occur you call a function () => ...
wich when executed till resolve the promise
/* but not with this one */
setTimeout( resolve('done'), 3000 );
here you actually resolve the promise (you execute the result function) and pass the result to the setTimeout function.
Writing
() => resolve( 'Job\'s done!!!' )
is the same as
function() {
resolve( 'Job\'s done!!!' );
}
setTimeout function with Promise resolve value as a callback
Overall, your explanation looks good enough. Just steps 4 & 5 have a bit wrong.
Step: 4 ....Since our 'resolve' is empty we can't use '.then' method's argument
RIGHT...
The answer is NO.
You still can .then
, and the data will be undefined
var wait = function (seconds) {
return new Promise(function (resolve) {
setTimeout(resolve, seconds * 1000);
});
};
wait(1)
.then(() => {
console.log(`1 second passed`);
}).then((data) => { console.log("Result: " + data) });
How do you wrap setTimeout in a promise
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();
});
});
How to control setTimeout with promises
You can't get a promise from a function that just calls setTimeout
- it needs some help, e.g.:
function after(n, f) {
return () => new Promise(resolve => {
setTimeout(() => {
resolve(f());
}, n);
});
}
with usage:
var functionsArray = [
after(100, () => console.log(1)),
after(200, () => console.log(2)),
after( 10, () => console.log(3)),
];
With that array you can then just await
each function in turn:
for (let f of functionsArray) {
await f();
}
Related Topics
Webdriver Click() VS JavaScript Click()
How to Capture an Arbitrary Number of Groups in JavaScript Regexp
How to Pass JSON Post Data to Web API Method as an Object
Convert String with Commas to Array
Convert Utc Epoch to Local Date
How to Add External Js Scripts to Vuejs Components
How to Detect If Browser Window Is Scrolled to Bottom
Window.Onload VS <Body Onload=""/>
How to Find Out What Character Key Is Pressed
Emberjs: How to Load Multiple Models on the Same Route
Display Date/Time in User's Locale Format and Time Offset
How to Order Events Bound with Jquery
Difference Between 'Return Await Promise' and 'Return Promise'
Promise - How to Force Cancel a Promise
Get the Closest Number Out of an Array
How to Escape a JSON String Containing Newline Characters Using JavaScript