2

Until Async/Await when we needed to make a recurrent thing N times every X seconds we had the choice between a for loop that wrap a settimeout or to use a wrapper around setinterval to count the number of times it was executed and clear it.

But with async await is it okay to do this:

async function newFashion() {
  for (let i = 0; i < 10; i++) {
    console.log(i);
    await sleep(1000);
  }
  console.log("Finish"); 
}
newFashion(); // 0 1 2 3 4 5 6 7 8 9 Finish

function oldFashion() {
  for (let i = 0; i < 10; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  }
  console.log("Finish");
}
oldFashion(); // Finish 0 1 2 3 4 5 6 7 8 9

function sleep(time) {
  return new Promise((resolve) => { 
    setTimeout(resolve, time);
  });
}

I know that the two don't behave the same. oldFashion() doesn't actually "pause" in the loop, but actually call the 10 setTimeout() while iterating, so basically he fills the event loop with 10 setTimeout().

newFashion() however behaves as it should be, he "stop" iterating when he hit the await, but I'm not sure exactly how that work.

So what is better, filling the event loop with N setTimeout(), or awaiting one after the other? And what is actually happening in newFashion()?

EDIT: To be a little more specific, what is the gain of each method in terms of performance? When N is small? When N is huge?

KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
Smirow
  • 125
  • 2
  • 8
  • I don't believe there is any problem with your new code, and I personally find it much easier to read. – Gerrit0 Dec 28 '17 at 20:46
  • 2
    Personally I prefer `const delay = time => new Promise(res=>setTimeout(res,time));` ;) – Cody G Dec 28 '17 at 20:55
  • @CodyG. Haha, we have to keep it simple here ;) – Smirow Dec 28 '17 at 20:57
  • The old way is less readable partly because the new way has a portion of the work moved out to a separate function. Also, the old way won't be very accurate. I wouldn't use a `for` loop in the first place for that. –  Dec 28 '17 at 20:58
  • If you're even more interested, https://stackoverflow.com/questions/38752620/promise-vs-settimeout https://www.youtube.com/watch?v=8aGhZQkoFbQ – Cody G Dec 28 '17 at 21:29
  • Another interesting notes on throttling of setTimeout https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified – Cody G Dec 28 '17 at 21:31

2 Answers2

2

Here's a better way to write an "old fashioned" version. It preserves the desired behavior of having the "Finish" log come last, and it doesn't rely on setting a series of timers immediately at different intervals in the future.

The whole thing is self contained and clear.

function betterOldFashion(i, n) {
  if (i < n) {
    console.log(i);
    setTimeout(betterOldFashion, 1000, i+1, n);
  } else {
    console.log("Finish");
  }
}
betterOldFashion(0, 10);

Your suggested "old fashioned" way looks less readable because you didn't abstract part of the behavior out to a separate function like you did with the "new" way, which you put underneath the "old fashioned" way for some reason.

To give both the same benefit, here's a rewrite of the way you were doing it, though not using imperative looping constructs in the first place is better, IMO.

function oldFashion() {
  for (let i = 0; i < 10; i++) {
    schedule(i, i);
  }
  schedule("Finish", 10);
}
oldFashion();

function schedule(msg, i) {
  setTimeout(() => {
    console.log(msg);
  }, i * 1000);
}

WRT performance, that's usually less of a concern for async code, and is difficult to measure. The solution at the top certainly has the least complexity, if that's any indicator of efficiency, and your version of the old way has to schedule many things immediately, creating more background work.

  • That second code snippet is really horrible. So fragile… – Bergi Dec 28 '17 at 21:21
  • @Bergi: It's basically the same as the OP's code, which as I suggested, is not desirable. I only reworked that solution to show the difference in readability obtained by moving code out to a function, which is what the OP did for the async/await version. –  Dec 28 '17 at 21:24
1

And what is actually happening in newFashion()?

The await expression causes the call of newFashion function to pause until sleep function's result, which is a Promise, will be resolved or rejected. After resolving or rejecting it continues the rest part of the code.

So what is better, filling the event loop with N setTimeout(), or awaiting one after the other?

As you can see, the async/await part is more readable. If the environment supports ES8, you can freely use it. async/await part also familiar programmers who came into Javascript from such languages as C#, which also uses async/await for asynchronous programming. And for them it is more handy to work with async/await instead of the for loop example which you have.

Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
  • It doesn't actually continue code execution after a rejected promise. `await` throws the reason for rejection if its operand promise is rejected. The throw then rejects the promise returned from calling the `async` function unless it is caught in a try/catch block. – traktor Dec 28 '17 at 21:04
  • @traktor53 You are right. But the control is anyway returned to the `async` function. Which can handle the error or throw it up through call stack – Suren Srapyan Dec 28 '17 at 21:06