Home > OS >  Refactor nested promises to a chain of promises
Refactor nested promises to a chain of promises

Time:06-21

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');
});
  • Related