I have simplified this problem. I have 3 functions which I want to run with a 2 second delay between each. The following code is working:
$.when(one()).done(function () {
$.when(delay(2000)).done(function () {
$.when(two()).done(function () {
$.when(delay(2000)).done(function () {
$.when(three()).done(function () {
console.log('finished');
});
});
});
});
});
function delay(ms) {
var waitForDelay = new $.Deferred();
setTimeout(function () {
waitForDelay.resolve().promise();
}, ms);
return waitForDelay.promise();
}
function one() {
console.log('one');
return new $.Deferred().resolve().promise();
}
function two() {
console.log('two');
return new $.Deferred().resolve().promise();
}
function three() {
console.log('three');
return new $.Deferred().resolve().promise();
}
However when I try to refactor it, it no longer waits for the delayed amounts of time:
one().done(delay(2000)).done(two).done(delay(2000)).done(three).done(function () {
console.log('finished');
});
How can I refactor to chain these promises rather than nesting them? I want to use jQuery for older browser compatibility.
CodePudding user response:
My advice would be to not use jQuery's weird promise-like data structures at all.
Have your functions return an actual Promise (either explicitly or by making them async functions) and await
each step in the chain.
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
// an async function implicitly returns a promise
const one = async () => {
console.log("one");
}
// or you can return one manually
const two = () => {
console.log("two");
return Promise.resolve();
}
const three = async () => {
console.log("three");
}
// await each step in an async function (an async IIFE in this case)
(async () => {
await one();
await delay(2000);
await two();
await delay(2000);
await three();
console.log("finished");
})();
You can also use .then()
if you prefer that style
// Convenience function so you don't repeat yourself
const waitTwoSeconds = () => delay(2000);
one()
.then(waitTwoSeconds)
.then(two)
.then(waitTwoSeconds)
.then(three)
.then(() => {
console.log("finished");
});
If you must use jQuery, it doesn't really look much different
function delay(ms) {
var d = $.Deferred();
setTimeout(d.resolve, ms);
return d.promise();
}
function one() {
console.log("one");
return $.Deferred().resolve().promise();
}
function two() {
console.log("two");
return $.Deferred().resolve().promise();
}
function three() {
console.log("three");
return $.Deferred().resolve().promise();
}
function waitTwoSeconds() {
return delay(2000);
}
one()
.then(waitTwoSeconds)
.then(two)
.then(waitTwoSeconds)
.then(three)
.then(function() {
console.log("finished");
});
<!-- Note jQuery 1.8 was the first to have chainable deferreds -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
I've used .then()
instead of .done()
because the latter does not chain and using $.when()
is unnecessary when .then()
is available.
CodePudding user response:
Taking inspiration from Phil's answer, here is a solution compatible with older browsers.
First, change the delay()
function to return a function:
function delay(ms) {
return function() {
var waitForDelay = new $.Deferred();
setTimeout(function () {
waitForDelay.resolve().promise();
}, ms);
return waitForDelay.promise();
}
}
All other functions stay the same as in the question.
Finally replace the .done()
s with .then()
s to make them chain:
one().then(delay(2000)).then(two).then(delay(2000)).then(three).then(function () {
console.log('finished');
});