Search code examples
javascriptpromiseasync-awaites6-promise

Javascript: Run async task in series(or sequence) without libraries


I want to run some asynchronous task in a loop, but it should execute in sequence order(one after another). It should be vanilla JS, not with any libraries.

var doSome = function(i) {
   return Promise.resolve(setTimeout(() => {
      console.log('done... ' + i)
   }, 1000 * (i%3)));
}

var looper = function() {
   var p = Promise.resolve();

   [1,2,3].forEach((n) => {
      p = p.then(() => doSome(n))
   })

   return p;
}

looper();

Current output:

calling for ...1
calling for ...2
calling for ...3
Promise {<resolved>: 8260}
done... 3
done... 1
done... 2

Expected output:

calling for ...1
calling for ...2
calling for ...3
Promise {<resolved>: 8260}
done... 1
done... 2
done... 3

Note: Kindly answer, if you tried and it's working as expected


Solution

  • So, from your comment below, I think your own example code isn't quite matching your description. I think what you want for your example is something closer to the below snippet:

    var doSome = function(i) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
      });
    }
    
    var looper = function() {
      [1,2,3].forEach((n) => {
        doSome(n).then(console.log);
      });
    }
    
    looper();

    Here, the array [1, 2, 3] is iterated over, and an asynchronous process is generated for each one. As each of those async processes complete, we .then on them and console log their resolved result.

    So, now the question comes how to best queue the results? Below, I stored them into an array, then leveraged async/await in order to pause execution on the results until they complete in order.

    // This is probably what you want
    var doSome = function(i) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
      });
    }
    
    var looper = async function() {
      const nums = [1,2,3];
      const promises = []
      nums.forEach((n) => {
        console.log(`Queueing ${n}`);
        promises.push(doSome(n));
      });
      for (let promise of promises) {
        const result = await promise;
        console.log(result);
      }
    }
    
    looper();

    Now, we could have eliminated a loop and only executed one after the last completed:

    // Don't use this-- it is less efficient
    var doSome = function(i) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
      });
    }
    
    var looper = async function() {
      const nums = [1,2,3];
      const promises = [];
      for (let n of nums) {
        console.log(`Queueing ${n}`);
        const result = await doSome(n);
        console.log(result);
      };
    }
    
    looper();

    But, as you can see in the log, this approach won't queue up the next async process until the previous one has completed. This is undesirable and doesn't match your use case. What we get from the two-looped approach preceding this one is that all async processes are immediately executed, but then we order/queue the results so they respect our predefined order, not the order in which they resolve.

    UPDATE

    Regarding Promise.all, async/await and the intended behavior of the queueing:

    So, if you want to avoid using async/await, I think you could write some sort of utility:

    var doSome = function(i) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
      });
    }
    
    function handlePromiseQueue(queue) {
      let promise = queue.shift();
      promise.then((data) => {
        console.log(data)
        if (queue.length > 0) {
          handlePromiseQueue(queue);
        }
      })
    }
    
    var looper = function() {
      const nums = [1,2,3];
      const promises = []
      nums.forEach((n) => {
        console.log(`Queueing ${n}`);
        promises.push(doSome(n));
      });
      handlePromiseQueue(promises);
    }
    
    looper();

    HOWEVER, let me be clear-- if user Bergi's assertion is correct, and it is not important that each async promise be executed upon as soon as it resolves, only that none of them be acted upon until they all have come back, then this can 100% be simplified with Promise.all:

    // This is probably what you want
    var doSome = function(i) {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve(`Completing ${i}`), 1000*(i%3))
      });
    }
    
    function handlePromiseQueue(queue) {
      let promise = queue.shift();
      promise.then((data) => {
        console.log(data)
        if (queue.length > 0) {
          handlePromiseQueue(queue);
        }
      })
    }
    
    var looper = function() {
      const nums = [1,2,3];
      const promises = []
      nums.forEach((n) => {
        console.log(`Queueing ${n}`);
        promises.push(doSome(n));
      });
      Promise.all(promises).then(() => handlePromiseQueue(promises));
    }
    
    looper();

    Finally, as Bergi also pointed out, I am playing fast and loose here by not setting up any catch on these various promises-- I omitted them for brevity in examples, but for your purposes you will want to include proper handling for errors or bad requests.