🌟 场景引入:为什么需要 sleep?
在开发中,我们有时希望“暂停”代码执行一段时间,比如:
- 模拟接口延迟
- 控制动画节奏
- 避免请求过于频繁
- 调试时观察中间状态
但在 JavaScript 中,没有原生的 sleep() 函数(不像 Python 的 time.sleep())。因为 JS 是单线程语言,不能真正“阻塞”主线程——否则页面会卡死!
那怎么办?答案是:用异步方式“假装暂停” 。
✨ 核心思路:用 Promise + setTimeout 实现 sleep
我们来看一段经典实现:
function sleep(n) {
return new Promise(resolve => {
setTimeout(resolve, n);
});
}
是不是简洁又优雅?它的使用方式如下:
sleep(3000)
.then(() => {
console.log('休息3秒');
})
.catch(() => {
console.log('出错了');
})
.finally(() => {
console.log('finally');
});
🔍 这段代码做了什么?
sleep(3000)返回一个 pending 状态的 Promise;- 3 秒后,
setTimeout触发resolve(),Promise 变为 fulfilled; .then()回调被加入微任务队列,随后执行;- 无论成功失败,
.finally()都会执行。
💡 注意:整个过程不会阻塞主线程!页面依然可以响应点击、滚动等操作。
🧠 深入解析:Promise 状态变化与事件循环
我们再看一个带调试日志的版本(如你提供的代码):
function sleep(n) {
let p;
p = new Promise(resolve => {
setTimeout(() => {
console.log('setTimeout 触发,准备 resolve');
console.log('此时 p 的状态:', p); // 实际上无法直接读取状态
resolve();
console.log('resolve 后 p 的状态:', p);
}, n);
});
return p;
}
虽然 console.log(p) 在浏览器中会显示 Promise {<pending>} 或 Promise {<fulfilled>},但JavaScript 并未提供 API 直接读取 Promise 状态。这是出于设计考虑——避免开发者过度依赖内部状态。
不过,通过 .then() 和 .finally(),我们可以感知状态变化:
- pending → fulfilled:
.then()和.finally()执行; - pending → rejected:
.catch()和.finally()执行。
⚠️ 在你的代码中,
sleep永远不会 reject,所以.catch()实际不会触发。但如果在setTimeout中抛出错误,就需要手动reject。
🛠️ 增强版:支持错误处理的 sleep
虽然基础版足够用,但我们可以让它更健壮:
function sleep(ms) {
if (ms < 0) {
return Promise.reject(new Error('睡眠时间不能为负数'));
}
return new Promise((resolve, reject) => {
setTimeout(() => {
// 可在此处模拟随机失败
if (Math.random() < 0.1) {
reject(new Error('随机故障'));
} else {
resolve();
}
}, ms);
});
}
这样,.catch() 就有意义了!
🚀 进阶用法:配合 async/await 更优雅
有了 sleep,我们就能写出类似同步代码的异步逻辑:
async function demo() {
console.log('开始');
await sleep(1000);
console.log('1秒后');
await sleep(2000);
console.log('再过2秒,总共3秒');
}
demo();
输出:
开始
(1秒后)1秒后
(再过2秒)再过2秒,总共3秒
✅ 这比嵌套
.then()更清晰,也更容易调试!
❓ 常见疑问解答
Q1:为什么不用 while 循环实现 sleep?
// 千万别这么干!
function badSleep(ms) {
const start = Date.now();
while (Date.now() - start < ms) {}
}
→ 这会完全阻塞主线程,页面无响应,用户体验极差!
Q2:setTimeout 是宏任务,会影响顺序吗?
是的!但 Promise.then 是微任务,会在当前宏任务结束后、下一个宏任务前执行。
不过对于 sleep 来说,这种差异通常不影响逻辑。
Q3:能取消 sleep 吗?
原生 setTimeout 支持 clearTimeout,但 Promise 一旦创建就无法取消。
若需可取消的 sleep,可结合 AbortController 或返回带有 cancel 方法的对象。
✅ 总结
| 要点 | 说明 |
|---|---|
| 核心实现 | new Promise(resolve => setTimeout(resolve, ms)) |
| 不阻塞主线程 | 利用异步机制“假装暂停” |
| 配合 async/await | 写出类同步的异步代码 |
| 状态变化 | pending → fulfilled(或 rejected)触发回调 |
| 不要阻塞 | 绝对避免用循环实现 sleep |
💡 结语
sleep 函数虽小,却浓缩了 JavaScript 异步编程的精华:用非阻塞的方式控制执行节奏。掌握它,不仅能解决实际问题,更能加深你对 Promise、事件循环的理解。
下次当你想“让代码歇一会儿”,记得:
不是让 JS 睡着,而是让它优雅地等待。