TypeScript 没有内置的 sleep。写一个返回 Promise<void> 的函数,让 setTimeout 在延迟结束后 resolve 它,然后在 async 函数里 await 它:
await sleep(300) 只会暂停 main。程序并没有被冻结:等待期间,定时器、其他异步函数和 I/O 都照常运行。
sleep 函数的原理
setTimeout(resolve, ms) 安排在 ms 毫秒后调用 resolve,而调用 resolve 会让 promise 完成。对这个 promise 使用 await,会把 async 函数挂起直到那一刻。
<void> 这个类型参数很重要。单独的 new Promise(...) 不会根据 resolve 的调用来推断类型,所以没有它,sleep 会返回 Promise<unknown>。而 void 正是允许不带值调用 resolve 的原因:在 Promise<number> 中,不带参数调用 resolve() 是编译错误。如果函数声明了返回类型,类型参数也可以写在那里:
setTimeout 保证的是最短时间,而不是精确时间:回调会在延迟过去并且事件循环空闲时执行。预计会有几毫秒的误差。
在循环中 sleep
在 for 或 for...of 循环里使用 await sleep(),会在每次迭代之间暂停,这就是控制节奏的方法(限流、轮询、终端里的动画):
这在 forEach 里行不通:它会忽略每个 async 回调返回的 promise,于是所有迭代同时开始,什么都不会等待。请使用 for 或 for...of 循环,详见 async/await 一页。
带延迟的重试
一个很常见的实际用法:尝试执行某个操作,如果失败就等一会儿再试,每次等待时间翻倍(指数退避)。
try 里的 return await fn() 很关键:没有 await,被拒绝的 promise 会在 catch 捕获到它之前就被返回。
可取消的 sleep
普通的 sleep 无法被打断。要提前结束等待(用户点了取消、程序开始关闭),就传入一个 AbortSignal,并在它触发时清除定时器:
由于那个长定时器已被清除,程序在 abort 后立即退出。没有 clearTimeout 的话,Node 会一直运行到 5 秒结束。
Node 内置的 Promise 定时器
Node 在 node:timers/promises 模块中提供了 promise 版本的 setTimeout,所以在 Node 代码里可以直接导入它,不用自己写辅助函数。它还接受一个用于 resolve 的值和一个 AbortSignal:
import { setTimeout as sleep } from "node:timers/promises";
async function main() {
await sleep(1000); // wait one second
const result = await sleep(500, "done"); // resolves with "done"
const ac = new AbortController();
await sleep(10_000, undefined, { signal: ac.signal }); // cancellable
}
类型来自 @types/node(npm i -D @types/node)。浏览器里没有这个模块,因此那个一行的辅助函数才是可移植的选择。
没有阻塞式的 sleep
有线程的语言都有一个阻塞当前线程的 sleep。JavaScript 在单线程和事件循环上运行你的代码,所以阻塞式的 sleep 会冻结其他一切:定时器不会触发,网络响应得不到处理,在浏览器中页面会失去响应。一个空转到时钟走过某个时间点的忙循环恰恰就会这样,它永远不是正确的工具。
异步的 sleep 就是 JavaScript 和 TypeScript 中等待的方式:函数被挂起,线程空出来,定时器触发时函数再继续执行。Atomics.wait 确实可以阻塞,但只能作用于 SharedArrayBuffer,而且浏览器禁止在主线程上使用;它是为协调 worker 线程而存在的,不是用来延时的。
常见问题
TypeScript 中如何实现 sleep?
定义 const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));,然后在 async 函数里调用 await sleep(1000)。这会让该函数暂停约一秒,而不会阻塞程序的其他部分。
TypeScript 有内置的 sleep 函数吗?
没有。TypeScript 只是给 JavaScript 加上类型,自己没有运行时库,而 JavaScript 没有 sleep。在 Node 中可以导入一个:import { setTimeout as sleep } from "node:timers/promises"。在浏览器里就自己写那个一行的辅助函数。
为什么 sleep 需要 await?
sleep(1000) 会立即返回一个 promise;只有 await 才会让外层的 async 函数暂停,直到它 resolve。没有 await,下一行会马上执行。await 只能用在 async 函数中(或 ES 模块的顶层)。
TypeScript 中能用同步的 sleep 阻塞线程吗?
在普通代码中不能。JavaScript 运行在单线程和事件循环上,忙等待的循环会冻结一切:定时器、网络回调、整个页面。请改用异步的 sleep。Atomics.wait 可以在 Node 或 worker 中阻塞线程,但它用于底层的 worker 协调,而不是用来延时。