0

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

TheLovelySausage
  • 3,838
  • 15
  • 56
  • 106
  • 1
    [This answer](https://stackoverflow.com/a/50319656/283366) from the duplicate looks like what you want – Phil Nov 22 '22 at 09:16
  • This answer is close but it seems to not be making use of callbacks though, I'm just updating my question to put a bit more emphasis on the callbacks instead of async/await – TheLovelySausage Nov 22 '22 at 09:43
  • 1
    Is using Promise ok? Or do you only want a solution with plain callbacks? – Nick Parsons Nov 22 '22 at 10:31
  • The Promise is mostly just to ensure that the callback is delayed for testing purposes but the callback is the mvp – TheLovelySausage Nov 22 '22 at 10:34
  • 1
    I'm reopening this question as it has been clarified that it is mainly about callbacks instead of promises/async/await (which the answers in the duplicate are referring to) – Nick Parsons Nov 22 '22 at 10:48
  • @NickParsons thank you! I should have been a bit more clear but I appreciate it – TheLovelySausage Nov 22 '22 at 10:57

1 Answers1

1

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.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • 1
    These look like exactly what I was hoping for (I didn't consider the Promise conflicting with the callback so thanks for that heads up too!) – TheLovelySausage Nov 22 '22 at 11:25