异步 API,比如 setTimeout , setInterval , requestIdleCallback 和requestAnimationFrame 还有 Promise ,这几个的区别?
面试先给结论
这几个都和“异步调度”有关,但它们不属于同一类机制,区别主要在:
- 进入哪种任务队列
- 执行时机
- 是否和浏览器渲染相关
- 是否会重复执行
- 适合什么场景
可以先用一句话概括:
setTimeout/setInterval属于宏任务调度;Promise.then属于微任务;requestAnimationFrame是浏览器下一帧渲染前执行;requestIdleCallback是浏览器空闲时执行。
一、先分类
| API | 类型 | 执行时机 | 是否重复 | 常见用途 |
|---|---|---|---|---|
setTimeout | 宏任务 | 指定时间后尽快执行 | 否 | 延迟执行、一次性任务 |
setInterval | 宏任务 | 每隔一段时间重复执行 | 是 | 轮询、周期任务 |
Promise.then | 微任务 | 当前同步代码执行完后,立即执行,在下一个宏任务前 | 否 | 异步链式调用、状态变更后立即处理 |
requestAnimationFrame | 渲染调度 | 浏览器下一次重绘前执行 | 否,需手动递归 | 动画、DOM 视觉更新 |
requestIdleCallback | 空闲调度 | 浏览器主线程空闲时执行 | 否,需手动递归 | 低优先级任务、非关键计算 |
二、一个个说区别
1.setTimeout
特点
- 指定一个最小延迟时间
- 时间到了以后,不是“立刻执行”,而是进入宏任务队列等待执行
- 只执行一次
示例
setTimeout(() => { console.log('timeout') }, 1000)注意点
即使写setTimeout(fn, 0),也不是马上执行,只是表示:
当前同步代码和当前轮事件循环任务处理完后,尽快执行
适用场景
- 延迟执行一段逻辑
- 让任务晚一点执行
- 模拟异步
- 节流/防抖内部实现
2.setInterval
特点
- 每隔固定时间把回调放入宏任务队列
- 会重复执行,直到手动清除
const timer = setInterval(() => { console.log('interval') }, 1000) clearInterval(timer)问题
如果回调执行时间太长,可能出现:
- 间隔不准
- 任务堆积
比如设置每 1 秒执行一次,但函数本身执行了 2 秒,那么就可能紊乱。
更推荐的替代方案
很多场景下,比起setInterval,更推荐:
function loop() { setTimeout(() => { console.log('do something') loop() }, 1000) } loop()因为这样可以保证“本次执行完,再开始下一次计时”。
适用场景
- 简单轮询
- 周期性检查
- 定时刷新
3.Promise
这里更准确地说,是:
Promise的回调,如then / catch / finally- 会进入微任务队列
特点
微任务执行时机比宏任务更早:
当前同步代码执行完
-> 先清空微任务队列
-> 再执行下一个宏任务
示例
console.log(1) setTimeout(() => { console.log(2) }, 0) Promise.resolve().then(() => { console.log(3) }) console.log(4)输出顺序:
1 4 3 2为什么
因为:
1、4是同步Promise.then是微任务setTimeout是宏任务
所以微任务先执行。
适用场景
- 异步结果处理
- 链式调用
- 需要“尽快但异步”执行的逻辑
4.requestAnimationFrame
这是和浏览器渲染帧相关的 API。
特点
- 回调会在下一次浏览器重绘前执行
- 更适合做动画
- 一般一帧约 16.6ms(60fps)
- 不会自动重复,需要自己递归调用
示例
function animate() { console.log('before paint') requestAnimationFrame(animate) } requestAnimationFrame(animate)为什么适合动画
因为它是跟着浏览器刷新节奏走的:
- 浏览器准备绘制下一帧时调用你
- 可以避免不必要的重绘
- 比
setInterval更平滑
适用场景
- 动画
- 平滑滚动
- 拖拽更新位置
- 与视觉变化强相关的 DOM 操作
和setTimeout做动画的区别
setTimeout:
- 不跟浏览器帧同步
- 容易掉帧
- 页面隐藏时也可能继续触发
requestAnimationFrame:
- 与浏览器重绘同步
- 更节能、更流畅
- 页面隐藏时通常会暂停或降频
5.requestIdleCallback
这是空闲回调。
特点
- 只有当浏览器主线程空闲时才执行
- 属于低优先级任务调度
- 不适合做实时、关键逻辑
- 兼容性不如前几个 API,好些项目会做降级处理
示例
requestIdleCallback((deadline) => { while (deadline.timeRemaining() > 0) { console.log('do idle work') } })参数deadline
它会告诉你当前空闲时间还剩多少:
deadline.timeRemaining()deadline.didTimeout
这样你可以把大任务拆开做。
适用场景
- 非紧急埋点处理
- 大数据分片计算
- 预加载
- 后台缓存整理
- 不影响首屏渲染的低优先级任务
不适合
- 动画
- 用户交互后的立即反馈
- 高优先级更新
三、它们的核心区别:执行优先级和时机
这个是面试官最想听的。
1. 同步 > 微任务 > 宏任务
先记住这个顺序:
同步代码先执行,当前调用栈清空后执行微任务,微任务清空后再执行宏任务
所以:
Promise.then比setTimeout更早执行setTimeout/setInterval是下一轮事件循环的宏任务
2.requestAnimationFrame跟“渲染”绑定
它不是单纯按时间排队,而是:
浏览器准备绘制下一帧时调用
所以它和动画更匹配。
3.requestIdleCallback跟“空闲”绑定
它也不是严格定时,而是:
浏览器有空才执行
所以适合低优先级任务。
四、简单理解成“谁更快”
这题经常有人答错,不能简单说谁绝对更快。
更准确的说法是:
Promise.then:在当前任务结束后优先执行,通常比setTimeout更早setTimeout:时间到了进入宏任务队列,不保证马上执行requestAnimationFrame:下一帧绘制前执行requestIdleCallback:空闲时执行,不保证很快setInterval:按间隔重复调度,但也会受主线程阻塞影响
五、典型面试代码题
例子 1
console.log('start') setTimeout(() => { console.log('setTimeout') }, 0) Promise.resolve().then(() => { console.log('promise') }) console.log('end')输出:
start end promise setTimeout原因
- 同步先执行:
start、end - 微任务:
promise - 宏任务:
setTimeout
例子 2
requestAnimationFrame(() => { console.log('raf') }) setTimeout(() => { console.log('timeout') }, 0)这个不一定在所有场景下都能机械地说谁先谁后,但面试里可以这么答:
requestAnimationFrame是在下一次重绘前执行,setTimeout是宏任务。具体顺序会受到浏览器调度、当前渲染时机影响,但从使用目的上,requestAnimationFrame是为视觉更新设计的,不建议和setTimeout用来做同类事情比较先后。
这样更严谨。
六、实际开发中怎么选
1. 想延迟执行一次
用:
setTimeout2. 想周期执行
简单场景:
setInterval更稳一些:
setTimeout` 递归3. 想在本轮同步代码之后尽快执行
用:
Promise.then或者框架里的 nextTick 本质上很多时候也是借助微任务。
4. 想做动画或 DOM 位移
用:
requestAnimationFrame5. 想把不重要的任务延后,避免影响交互
用:
requestIdleCallback七、面试标准回答模板
你可以直接背这段:
这几个异步 API 的主要区别在于它们的调度机制和执行时机不同。
setTimeout和setInterval属于宏任务,前者是延迟一次执行,后者是周期性执行;Promise.then属于微任务,会在当前同步代码执行结束后、下一个宏任务之前执行,所以通常比setTimeout更早;requestAnimationFrame是在浏览器下一次重绘前执行,适合做动画和视觉更新;requestIdleCallback是在浏览器空闲时执行,适合做低优先级、不影响用户交互的任务。
所以实际开发中,如果是异步结果处理我会用 Promise,如果是动画我会优先用 requestAnimationFrame,如果是低优先级后台任务会考虑 requestIdleCallback。
八、再补一个“事件循环”层面的回答
如果面试官继续追问,你可以再说:
从事件循环角度看,
Promise.then进入微任务队列,setTimeout和setInterval进入宏任务队列;每次宏任务执行结束后,JS 引擎会先清空微任务队列,再决定是否进行渲染。requestAnimationFrame是浏览器在渲染前调用,requestIdleCallback是浏览器在空闲阶段调用,所以它们更偏向浏览器调度,而不只是普通任务队列。
这段比较加分。
九、30 秒短答版
setTimeout和setInterval是宏任务,分别用于延迟一次执行和定时重复执行;Promise.then是微任务,执行优先级比宏任务高;requestAnimationFrame是浏览器下一帧渲染前执行,适合动画;requestIdleCallback是浏览器空闲时执行,适合低优先级任务。
所以它们的核心区别就是:任务队列不同、执行时机不同、适用场景不同。
十、一句话总结
Promise看“任务优先级”,setTimeout/setInterval看“时间”,requestAnimationFrame看“渲染帧”,requestIdleCallback看“空闲时间”。`
