Node.js 面试题详细答案 - Q6
Q6: setImmediate vs setTimeout(fn, 0) 的执行顺序有什么区别?
基本概念
setImmediate
- 在事件循环的 check 阶段 执行
- 在当前事件循环结束后执行
- 优先级高于 setTimeout
setTimeout(fn, 0)
- 在事件循环的 timers 阶段 执行
- 最小延迟为 1ms(实际可能更长)
- 优先级低于 setImmediate
基本执行顺序
console.log('开始')
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
console.log('结束')
详细执行顺序分析
在 I/O 回调中的执行顺序
const fs = require('fs')
fs.readFile(__filename, () => {
console.log('=== I/O 回调中 ===')
setTimeout(() => {
console.log('setTimeout in I/O')
}, 0)
setImmediate(() => {
console.log('setImmediate in I/O')
})
console.log('I/O 回调结束')
})
console.log('主线程结束')
为什么在 I/O 回调中 setImmediate 先执行?
复杂场景示例
嵌套调用
console.log('1. 开始')
setTimeout(() => {
console.log('2. setTimeout 外层')
setTimeout(() => {
console.log('3. setTimeout 内层')
}, 0)
setImmediate(() => {
console.log('4. setImmediate 内层')
})
}, 0)
setImmediate(() => {
console.log('5. setImmediate 外层')
setTimeout(() => {
console.log('6. setTimeout 内层')
}, 0)
setImmediate(() => {
console.log('7. setImmediate 内层')
})
})
console.log('8. 结束')
与微任务结合
console.log('1. 开始')
setTimeout(() => {
console.log('2. setTimeout')
}, 0)
setImmediate(() => {
console.log('3. setImmediate')
})
Promise.resolve().then(() => {
console.log('4. Promise')
})
process.nextTick(() => {
console.log('5. process.nextTick')
})
console.log('6. 结束')
实际应用场景
使用 setImmediate 优化性能
function processLargeArray(array, callback) {
let index = 0
const chunkSize = 1000
function processChunk() {
const start = Date.now()
while (index < array.length && Date.now() - start < 5) {
processItem(array[index])
index++
}
if (index < array.length) {
setImmediate(processChunk)
} else {
callback()
}
}
processChunk()
}
function processItem(item) {
return item * 2
}
使用 setTimeout 实现延迟
function delayedExecution() {
console.log('开始延迟执行')
setTimeout(() => {
console.log('延迟 100ms 后执行')
}, 100)
setTimeout(() => {
console.log('延迟 200ms 后执行')
}, 200)
setImmediate(() => {
console.log('立即执行(下一个事件循环)')
})
}
delayedExecution()
性能对比
执行时间对比
function performanceTest() {
const iterations = 1000000
console.time('setTimeout')
for (let i = 0; i < iterations; i++) {
setTimeout(() => {}, 0)
}
console.timeEnd('setTimeout')
console.time('setImmediate')
for (let i = 0; i < iterations; i++) {
setImmediate(() => {})
}
console.timeEnd('setImmediate')
}
performanceTest()
最佳实践
何时使用 setImmediate
fs.readFile('file.txt', (err, data) => {
if (err) throw err
setImmediate(() => {
console.log('文件处理完成')
})
})
function heavyTask() {
let result = 0
let i = 0
function processChunk() {
const start = Date.now()
while (i < 1000000 && Date.now() - start < 5) {
result += i
i++
}
if (i < 1000000) {
setImmediate(processChunk)
} else {
console.log('任务完成:', result)
}
}
processChunk()
}
何时使用 setTimeout
function delayedTask() {
setTimeout(() => {
console.log('延迟 1 秒后执行')
}, 1000)
}
function periodicTask() {
setTimeout(() => {
console.log('定时任务')
periodicTask()
}, 1000)
}
总结
- setImmediate:在 check 阶段执行,优先级高,适合立即执行
- setTimeout(fn, 0):在 timers 阶段执行,优先级低,适合延迟执行
- 执行顺序:在主线程中 setImmediate 先执行,在 I/O 回调中 setImmediate 先执行
- 使用场景:setImmediate 用于性能优化,setTimeout 用于延迟执行
- 最佳实践:根据具体需求选择合适的 API