Skip to content

sleep:优雅控制异步时间流

在 JavaScript 的异步世界中,时间是最基本的维度之一。无论是模拟网络延迟、实现重试间隔,还是控制动画节奏,我们都需要一种简洁、可读的方式来“暂停”执行。

sleep(ms) 函数正是为此而生——它提供了一种比 setTimeout 更优雅、更符合现代异步编程范式的延迟机制。

为何 sleepsetTimeout 更优雅?

传统方式: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 语法。
  • 可组合:能轻松嵌入 piperetry 等函数式流水线。

类比setTimeout 像“设置闹钟”,而 sleep 像“小睡一会儿”——前者是事件驱动,后者是流程控制。

实现:一行代码的精髓

ts
const sleep = (ms: number): Promise<void> =>
  new Promise(resolve => setTimeout(resolve, ms))

关键点解析

  1. 返回 Promise<void>

    • 表示这是一个“无返回值”的异步操作。
    • 调用者可 await 它,但不会获取有意义的值。
  2. new Promise + setTimeout

    • setTimeout(resolve, ms)ms 毫秒后调用 resolve,使 Promise 进入 fulfilled 状态。
    • 此时 await sleep(ms) 会继续执行后续代码。
  3. 类型安全

    • 明确 msnumber,防止传入字符串等无效值。
    • 返回 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 函数虽小,却深刻改变了我们对时间的编程方式。

它将“等待”这一抽象概念,封装为一个可 awaitPromise,使异步代码的时间维度变得清晰、可控、可组合。

当你写下 await sleep(1000) 时,你不仅在暂停代码,更在声明式地描述时间的流逝

在构建用户交互、网络请求、自动化流程时,sleep 是你手中最简单却最强大的“时间控制器”。

正如函数式编程教会我们“组合”,sleep 教会我们:在异步的世界里,优雅的等待,也是一种能力