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) );
如果您将其设置为 1
和 2
而不是 0
和 1
,您的期望就会实现。
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)
我一直在玩 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, thesetTimeout
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) );
如果您将其设置为 1
和 2
而不是 0
和 1
,您的期望就会实现。
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)