实现可取消的 sleep,并按顺序每隔一段时间打印 1、2、3。
先理解原理和示例,再练习完整口述与追问。
回答要点
sleep 返回在 ms 毫秒后 resolve 的 Promise;支持 AbortSignal 时,开始前已取消直接拒绝,取消时清除定时器并拒绝,完成时移除监听。顺序打印用 for...of 加 await sleep 串行;如果题意是“在第 1、2、3 秒分别打印”,也可以一次性按 i * interval 设置定时器。
原理拆解
同一题常有两种理解:每次打印之间间隔 interval(串行等待,总耗时随前一步变化),或者从开始计时在固定时刻打印(定时器并行登记)。面试时先确认题意。经典的 for (var i = 1; i <= 3; i++) setTimeout(() => console.log(i), i * 1000) 会打印三个 4,因为三个回调共享同一个函数作用域的 i;用 let 每轮创建新绑定即可。
取消逻辑要防止泄漏:正常完成后移除 abort 监听;{ once: true } 保证监听只触发一次。signal.reason 是调用 abort(reason) 时传入的原因,未传时为 AbortError。
示例与推演
function sleep(ms, { signal } = {}) {
return new Promise((resolve, reject) => {
if (signal && signal.aborted) {
reject(signal.reason)
return
}
const onAbort = () => {
clearTimeout(timer)
reject(signal.reason)
}
const timer = setTimeout(() => {
if (signal) signal.removeEventListener('abort', onAbort)
resolve()
}, ms)
if (signal) signal.addEventListener('abort', onAbort, { once: true })
})
}
async function printInOrder(values, interval, print = console.log, signal) {
for (const value of values) {
await sleep(interval, { signal })
print(value)
}
}
function printAtFixedTimes(values, interval, print = console.log) {
const timers = values.map((value, index) => setTimeout(() => print(value), (index + 1) * interval))
return () => timers.forEach(clearTimeout)
}
推演:printInOrder([1, 2, 3], 1000) 在约 1、2、3 秒时依次打印;中途调用 controller.abort(),正在等待的 sleep 拒绝,循环随之停止,后续数字不再打印。
易错点
取消时只 reject 不清除定时器,定时器仍会执行;await 写在 forEach 回调里不会串行(见宝典 4)。
参考回答可直接练习口述
sleep 返回一个定时 resolve 的 Promise;要支持取消,就接收 AbortSignal,已取消直接 reject,取消时清掉定时器并 reject,正常完成时移除监听。按顺序打印用 for...of 循环加 await sleep,每次间隔固定时间;如果题目要求在第 1、2、3 秒打印,也可以一次性按 index 乘间隔设置定时器,注意要用 let 而不是 var。