sleep:优雅控制异步时间流
在 JavaScript 的异步世界中,时间是最基本的维度之一。无论是模拟网络延迟、实现重试间隔,还是控制动画节奏,我们都需要一种简洁、可读的方式来“暂停”执行。
sleep(ms) 函数正是为此而生——它提供了一种比 setTimeout 更优雅、更符合现代异步编程范式的延迟机制。
为何 sleep 比 setTimeout 更优雅?
传统方式:setTimeout 的局限
ts
// 回调地狱
setTimeout(() => {
console.log('3秒后执行')
// 下一个延迟...
setTimeout(() => {
console.log('再2秒后')
}, 2000)
}, 3000)- 嵌套深:多个延迟时代码难以阅读。
- 非阻塞但难链式:
setTimeout是异步的,但无法直接用await。
sleep 的优势:await sleep(1000)
ts
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
await sleep(3000)
console.log('3秒后执行')
await sleep(2000)
console.log('再2秒后')- 线性代码流:逻辑从上到下,清晰直观。
- 可
await:完美融入async/await语法。 - 可组合:能轻松嵌入
pipe、retry等函数式流水线。
类比:setTimeout 像“设置闹钟”,而 sleep 像“小睡一会儿”——前者是事件驱动,后者是流程控制。
实现:一行代码的精髓
ts
const sleep = (ms: number): Promise<void> =>
new Promise(resolve => setTimeout(resolve, ms))关键点解析
返回
Promise<void>:- 表示这是一个“无返回值”的异步操作。
- 调用者可
await它,但不会获取有意义的值。
new Promise+setTimeout:setTimeout(resolve, ms)在ms毫秒后调用resolve,使Promise进入fulfilled状态。- 此时
await sleep(ms)会继续执行后续代码。
类型安全:
- 明确
ms为number,防止传入字符串等无效值。 - 返回
Promise<void>,IDE 可正确推导。
- 明确
用途:sleep 的五大实战场景
1. 模拟网络延迟(开发/测试)
ts
const fetchUser = async (id: number): Promise<User> => {
await sleep(1500) // 模拟 1.5s 网络延迟
return api.getUser(id)
}- 用于开发环境模拟慢速网络,测试 UI 加载状态。
- 避免依赖真实网络,提升测试稳定性。
2. 重试机制中的退避间隔
ts
const retry = async <T>(fn: () => Promise<T>, retries = 3): Promise<T> => {
for (let i = 0; i < retries; i++) {
try {
return await fn()
} catch (error) {
if (i === retries - 1) throw error
await sleep(1000 * Math.pow(2, i)) // 指数退避
}
}
}sleep是实现指数退避(Exponential Backoff)的基础。- 让服务有时间恢复,避免重试风暴。
3. 控制动画或 UI 节奏
ts
const typeText = async (element: HTMLElement, text: string) => {
for (const char of text) {
element.textContent += char
await sleep(100) // 每100ms打一个字
}
}- 实现打字机、渐显等动画效果。
- 比
setInterval更精确控制每一步。
4. 限流(Rate Limiting)的简单实现
ts
let lastCall = 0
const limitedFetch = async (url: string) => {
const now = Date.now()
const delay = Math.max(0, 1000 - (now - lastCall)) // 每秒最多一次
if (delay > 0) await sleep(delay)
lastCall = Date.now()
return fetch(url)
}- 简单实现 API 调用频率限制。
- 比复杂的令牌桶更易理解。
5. 流水线中的时间控制
ts
const workflow = async () => {
await step1()
await sleep(500) // 短暂暂停
await step2()
await sleep(1000) // 等待用户反应
await step3()
}- 在
asyncPipe中插入时间延迟。 - 控制自动化流程的节奏。
高级技巧:可取消的 sleep
标准 sleep 无法中途取消。若需取消(如用户导航离开页面),可结合 AbortSignal:
ts
const sleep = (ms: number, signal?: AbortSignal): Promise<void> => {
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, ms)
if (signal) {
signal.addEventListener('abort', () => {
clearTimeout(timer)
reject(new Error('Sleep cancelled'))
})
}
})
}
// 使用
const controller = new AbortController()
await sleep(5000, controller.signal) // 可被 controller.abort() 中断适用于长时间等待且需要响应用户操作的场景。
结语:sleep 是时间的“语法糖”
sleep 函数虽小,却深刻改变了我们对时间的编程方式。
它将“等待”这一抽象概念,封装为一个可 await 的 Promise,使异步代码的时间维度变得清晰、可控、可组合。
当你写下 await sleep(1000) 时,你不仅在暂停代码,更在声明式地描述时间的流逝。
在构建用户交互、网络请求、自动化流程时,sleep 是你手中最简单却最强大的“时间控制器”。
正如函数式编程教会我们“组合”,sleep 教会我们:在异步的世界里,优雅的等待,也是一种能力。