Home > front end >  Chaining setTimeout Calls Recursively with Javascript Callbacks
Chaining setTimeout Calls Recursively with Javascript Callbacks

Time:11-23

I have a simple setTimeout function in Javascript that just allows me to specify the amount of time to delay whatever action by and then just a callback to use for chaining

function delay(item, callback) {
    return new Promise(function(response, reject) {
        setTimeout(function() {
            console.log(item.message);
            response(callback());
        }, item.time);
    });
}

I'm able to use it fine with nesting callbacks but it starts to become very tedious and ugly to use for longer chains

function delayChain() {

    const items = [
        {message:"Waited 01 sec", time:1000},
        {message:"Waited 02 sec", time:2000},
        {message:"Waited 04 sec", time:4000},
        {message:"Waited 03 sec", time:3000}
    ];

    delay(items[0], function() {
        delay(items[1], function() {
            delay(items[2], function() {
                delay(items[3], function() {
                    console.log("Done Waiting");
                });
            });
        });
    });

}

I was wondering if it is possible to do something similar but in a recursive way

UPDATE

It seems that something similar can be done without the need of callbacks by using async/await like this

async function delayChainAsync() {

    const items = [
        {message:"Waited 01 sec", time:1000},
        {message:"Waited 02 sec", time:2000},
        {message:"Waited 04 sec", time:4000},
        {message:"Waited 03 sec", time:3000}
    ];

    for(let item of items) {
        await delay(item, function() {});
    }

    console.log("Done Waiting");

}

But I'm hoping to still make use of the callback chaining like in the original delay function

CodePudding user response:

One approach is to create the nested functions using a loop on items. The idea is to loop backward through items, creating the most inner nested function first, and keeping a reference to it so that when you move to the next item down your array, you can create the next nested function that uses the previous one you created as the callback to delay. Once the loop is finished, you can call the final function you created to initiate the outer most function to begin the callback chain:

function delay(item, callback) {
  setTimeout(function() {
    console.log(item.message);
    callback();
  }, item.time);
}

function delayChain() {
  const items = [ {message:"Waited 01 sec", time:1000}, {message:"Waited 02 sec", time:2000}, {message:"Waited 04 sec", time:4000}, {message:"Waited 03 sec", time:3000} ];
  
  let nextFn = () => console.log("Done waiting"); // the most inner nested function
  for(let i = items.length - 1; i >= 0; i--) {
    const ref = nextFn; // required so that the closure below refers to this current function and doesn't change as we update `nextFn` through our loop iterations
    nextFn = function() {
      delay(items[i], ref);
    };
  }
  nextFn();
}

delayChain();

Another option is to do this "pseudo recursively" by passing a counter/index to delayChain to determine which object we need to create the delay for in each new call to delayChain:

function delay(item, callback) {
  setTimeout(function() {
    console.log(item.message);
    callback();
  }, item.time);
}

function delayChain(i = 0) {
  const items = [ {message:"Waited 01 sec", time:1000}, {message:"Waited 02 sec", time:2000}, {message:"Waited 04 sec", time:4000}, {message:"Waited 03 sec", time:3000} ];

  if(i === items.length)
    console.log("Done waiting"); // the most inner nested function
  else
    delay(items[i], () => delayChain(  i)); // recursively call `delayChain`
}

delayChain();

Also, note that your delay function does not need to return a Promise. As you're trying to work with callbacks, there is no need for it to return a Promise as that Promise value is never used. Most of the time, your function should either accept a callback as an argument or return a promise, but not both.

  • Related