5

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

Kamalakannan J
  • 2,818
  • 3
  • 23
  • 51
  • The only reason the code is executing in the order 3, 1, 2 is that your modulus math is wrong. `1%3=1`, `2%3=2` but `3%3=0`, so for 1 it is waiting 1000ms, 2 it is waiting 2000ms, but for 3 it is waiting 0ms. – Alexander Nied Apr 10 '18 at 21:35
  • Perhaps relevant here: [How to synchronize a sequence of promises](https://stackoverflow.com/questions/29880715/how-to-synchronize-a-sequence-of-promises/29906506#29906506) and [ES6 promises async each](https://stackoverflow.com/questions/32028552/es6-promises-something-like-async-each/32028826#32028826) and [Make .forEach() wait before going onto the next iteration](https://stackoverflow.com/questions/28983424/make-angular-foreach-wait-for-promise-after-going-to-next-object/28983668#28983668) – jfriend00 Apr 10 '18 at 23:08
  • 1
    `Promise.resolve(setTimeout(` wut... you realize that resolves immediately with a number, and therefore doesn't wait on the timeout, right? – Kevin B Apr 11 '18 at 15:40
  • I don't see where in your code the `calling for ...` is logged? – Bergi Apr 11 '18 at 16:05
  • @KevinB - the code as posed in the question is not quite achieving what the user intends, I don't believe. The first snippet in my answer below I think is a clearer representation of what the user is trying to describe. – Alexander Nied Apr 11 '18 at 16:16
  • @AlexanderNied Maybe so, but i'd rather the op clarify the question. If that promise was created correctly, the code would otherwise work as is. – Kevin B Apr 11 '18 at 16:18

1 Answers1

3

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.

Alexander Nied
  • 12,804
  • 4
  • 25
  • 45
  • It's not by mistake I did so, I intentionally want to mimic that the last task is short task compared to the others. Even the task is small, it should run in the sequence. That's my use case. Tasks of varying execution time should be run in the called sequence. This is my use case. You don't even have to correct this one, just give a new example. – Kamalakannan J Apr 11 '18 at 04:36
  • 1
    @KamalakannanJ - OK, let me make sure I understand your use case then. You have three async processes that will kick off simultaneously, but you then want to make sure that each of their callbacks/`.then` functions execute in a specific order regardless of the order in which the async processes resolve? – Alexander Nied Apr 11 '18 at 04:40
  • You're right @Alexander. It will be kicked off from a for-loop and it should be executed in the same order after one another. – Kamalakannan J Apr 11 '18 at 10:51
  • @KamalakannanJ - I've updated based on your comments and my new understanding of your requirements. Please review and if it fulfills your needs mark it as the selected answer. If not, please comment to explain how this fails to meet your needs. – Alexander Nied Apr 11 '18 at 15:36
  • 2
    Your second snippet is dangerous, [don't create multiple promises and sequentially `await` them but use `Promise.all` instead](https://stackoverflow.com/questions/46889290/waiting-for-more-than-one-concurrent-await-operation)! – Bergi Apr 11 '18 at 16:08
  • 1
    @Bergi - the problem is that he doesn't want to do _one_ thing after _all_ promises complete, he wants to fire off all asyncs at once, but queue each of their callbacks in a specific order and fire each one as soon as possible. I see the risk with "what if one fails", but a `catch` that broke the loop could probably mitigate that, no? – Alexander Nied Apr 11 '18 at 16:14
  • @AlexanderNied I don't see where you got "as soon as possible" from, imo `for (const result of await Promise.all(promises)) console.log(result);` is enough and has the expected output. If you want to use your approach, yes, you will need to explicitly `.catch` errors (and if only to ignore them) of all promises that you create without immediately awaiting them. – Bergi Apr 11 '18 at 16:25
  • @Bergi - I inferred it from the text "execute in sequence order(one after another)" and from the manner in which the user seems to be coding it to execute as quickly as possible. I 100% agree with you that, if I am interpreting his intent incorrectly, and he just wants to wait until they all complete and then start executing responses in order, then `Promise.all` is of course the cleaner, safer, more expressive approach. – Alexander Nied Apr 11 '18 at 16:34
  • @AlexanderNied It's working as Expected. Thanks. Just one doubt, is there any way to do without `async/await` ? – Kamalakannan J Apr 12 '18 at 07:45
  • @Bergi I'm not getting the expected results with `Promise.all`, because all the promises are triggered simultaneously, so the 3rd promise(which has 0ms as timeout) ran first. – Kamalakannan J Apr 12 '18 at 07:45
  • @KamalakannanJ - I have updated the answer with a solution that avoids `async`/`await`, as well as a solution that works with `Promise.all`. I would recommend you pay careful attention to the latter, as it is the cleanest option _**if**_ it meets your requirements. Good luck! – Alexander Nied Apr 12 '18 at 15:01
  • Both of the new solutions work, but I'm confused with how the latter works. Because, in both you're calling `handlePromiseQueue`, so does the promises execute twice in latter ? – Kamalakannan J Apr 13 '18 at 07:15
  • @KamalakannanJ - `handlePromiseQueue` is a [recursive](https://developer.mozilla.org/en-US/docs/Glossary/Recursion) stand-in for a while loop-- each it runs, it evaluates the array, shifts the first promise off the array and `.then`s it, and when it is done it re-calls itself with the same array. Once there are no items left in the array it exits. In the `Promise.all` example, we simply wait until all the promises have resolved before we start calling `.then` on them, whereas in the previous example we start `.then`ing them as soon as they are resolved and next in the queue. – Alexander Nied Apr 13 '18 at 16:32