Promise in setTimeOut loop: return resolve() or if

2019-08-19 10:04发布

问题:

TL;DR: It appears Promise's resolve() returns, which in a looping function causes the promise to keep running. What's the proper way to call resolve() for looping promises?

Details:
I built a setTimeOut loop for animation purposes which would run for a number of times, then exit the loop.

Simply calling resolve when done did not work: it did resolve the promise but then kept running.

function timeoutLoop (fcn, steps = -1, time = 0) {
  return new Promise((resolve) => {
    function timeoutStep () {
      if (steps-- === 0) resolve() // steps will be -1
      fcn()
      setTimeout(timeoutStep, time)
      console.log(steps)
    }
    timeoutStep()
  })
}
timeoutLoop(() => {}, 10).then(() => console.log('done'))

Two fixes worked: use an if-else or have the if use return resolve()

Use if-else

  if (steps-- === 0) {
    resolve() // steps will be -1
  } else {
    fcn()
    setTimeout(timeoutStep, time)
    console.log(steps)
  }

return the resolve() call

if (steps-- === 0) return resolve() // steps will be -1

My concern is that, even if both "work", there may be side effects I don't understand such as the call-stack retaining the call frame of the Promise body.

What's the best practice in this case?

Edit: The answer @Bergi gave, using async functions, is clearly the right philosophy. I did make one minor change, using a while loop rather than recursion:

function timeoutPromise( ms = 1000) { return new Promise(resolve => { setTimeout(resolve, ms) }) } async function timeoutLoop (fcn, steps = -1, time = 0) { while (steps-- !== 0) { // Note decr occurs *after* comparison fcn() await timeoutPromise(time) } } timeoutLoop(() => {}, 10, 1000).then(() => console.log('done'))

回答1:

resolve() is not syntax, it does not stop execution of anything like a return statement does. You have to explicitly not call setTimeout again to prevent it from occurring. Both early return and else work fine for you there.

What's the best practice in this case?

Not to do any complicated stuff in non-promise asynchronous callbacks.

Promisify on the lowest possibly level - the setTimeout function and only that. Write your program logic with promises.

function delay(t) {
  return new Promise(resolve => { setTimeout(resolve, t); });
}
async function timeoutLoop (fcn, steps = -1, time = 0) {
  if (steps !== 0) {
    fcn()
    console.log(steps);
    await delay(time)
    return timeoutLoop(fcn, steps-1, time);
  }
}