Javascript、Promises 和 setTimeout

Javascript, Promises and setTimeout

我一直在玩 Promises,但我无法理解以下代码所发生的事情:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 10)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')

setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

输出为:


Promise started - Async code started 
Promise made - Sync code terminated 
Promise log inside first setTimeout 
Promise log inside second setTimeout 
Promise log after fulfilled

符合预期。

但让我们检查以下代码的输出:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 1)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 0)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')
setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 0)

Changed the to be resolved promise setTimeout timer value from 10ms to 1ms

输出为:

Promise started - Async code started 
Promise made - Sync code terminated 
Promise log after fulfilled 
Promise log inside first setTimeout 
Promise log inside second setTimeout 

有什么解释吗?

来自Concurrency model and the event loop

  • setTimeout does not run immediately after its timer expires
  • Zero delay doesn't actually mean the call back will fire-off after zero milliseconds. Calling setTimeout with a delay of 0 (zero) milliseconds doesn't execute the callback function after the given interval. Basically, the setTimeout needs to wait for all the code for queued messages to complete even though you specified a particular time limit for your setTimeout.

如果我们设置 2 和 1 毫秒会发生什么:

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 2)
})

console.log('Promise log inside first setTimeout 1')
setTimeout(() => {
  console.log('Promise log inside first setTimeout 2')
}, 1)

promise.then(res => {
  console.log('Promise log after fulfilled ❌')

})

console.log('Promise log inside second setTimeout 1')
setTimeout(() => {
  console.log('Promise log inside second setTimeout 2')
}, 1)
});

输出总是:

Promise started - Async code started
Promise log inside first setTimeout 1
Promise log inside second setTimeout 1
Promise log inside first setTimeout 2
Promise log inside second setTimeout 2
Promise log after fulfilled ❌

结论

如果你想要一个适当的行为,值得摆脱零延迟。

我用下面的例子来说明:

setTimeout(() => {
  console.log('1 ms timeout');
}, 1);                            // Moved to async queue at time = T0
setTimeout(() => {
  console.log('0 ms timeout')
}, 0);                            // Moved to async queue after 1 ms that synchronous call to setTimeout takes i.e. at T1
                                  // So at T1, queue will be [("1ms timeout", 0), ("0ms timeout", 0)]

因此这将打印

1 ms timeout
0 ms timeout

以上理解: 调用 setTimeouts 是同步的(即使它的回调放在异步队列中),即我们调用 setTimeout() 并移动到下一条语句——这个同步操作本身可能需要 1 毫秒。

换句话说,1ms 太短了,所以当 JS 引擎看到第二个异步语句时,第一个已经在队列中花费了 1ms。

我还建议您尝试以下方法

setTimeout(() => {
  console.log("First");
}, 2);                      // queue at T0 = [("First", 2)]

const forLoopLimit = 100;
for (var i = 0; i < forLoopLimit; i++){
    console.log(i * 10000);
}                           // Assume that it takes about 3 milliseconds
                            // queue at T3 = [("First", 0)]
setTimeout(() => {
  console.log("Second");
}, 0);                      // Assume it takes 0 milliseconds.
                            // queue at T4 = [("First", 0), ("Second", 0)]

这将在 Second 之前打印 First,即使前者的超时时间为 2 毫秒,而后者的超时时间为 0 毫秒。 现在将 forLoopLimit 改为 1 甚至 10,你会看到同步任务现在不需要 3 毫秒,并且 Second 打印在 First

之前

另外值得一试的是:

console.log(Date.now());
console.log(Date.now());

多次尝试上面的操作,您会发现有时控制台日志会有不同的时间戳。粗略地说,您可以说 console.log()Date.now() 需要 0.5 毫秒。只不过是调用/执行同步内容的时候了。

Chrome has an hardcoded minimum timeout of 1ms.

  base::TimeDelta interval_milliseconds =
      std::max(base::TimeDelta::FromMilliseconds(1), interval);

因此对于 Chrome,所有 setTimeout( fn , 0 ) 都转换为 setTimeout( fn , 1 ),因此计划在您执行的第一个计划之后触发(记住 Promise 构造函数是同步调用的)。

所以我们实际上可以用

简化你的例子

setTimeout( () => console.log( '1ms delay' ), 1 );
setTimeout( () => console.log( '0ms delay' ), 0 );

在Chrome中,1ms延迟总是先触发,这与常识相反,因为在内部它实际上是:

setTimeout( () => console.log( '1ms delay' ), Math.max(1, 1) );
setTimeout( () => console.log( '0ms delay' ), Math.max(1, 0) );

如果您将其设置为 12 而不是 01,您的期望就会实现。

const promise = new Promise((resolve, reject) => {
  console.log('Promise started - Async code started')
  setTimeout(() => {
    resolve('Success')
  }, 2)
})

setTimeout(() => {
  console.log('Promise log inside first setTimeout')
}, 1)

promise.then(res => {
  console.log('Promise log after fulfilled')
})

console.log('Promise made - Sync code terminated')
setTimeout(() => {
  console.log('Promise log inside second setTimeout')
}, 1)