atomic-sleep: 一个名副其实的JavaScript sleep函数
某些情况下,我们需要让程序休眠一段时间再继续运行。
Node.js中通常的实现是这样的:
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
如果你需要休息2秒,则这样使用:
// do something
await sleep(2000);
// do something else
但实际上该 ` sleep ` 函数只对当前调用有效,无法阻止其它异步函数的继续执行。一个简单的证明:
[1, 2, 3].map(async v => {
console.log(new Date(), "sleep", v, "start");
await sleep(2000);
console.log(new Date(), "sleep", v, "over");
运行上述代码,终端上出现如下信息:
我们看到,尽管 1 中调用了 sleep , 2 和 3 几乎也在同一时间在运行着。尽管我们一共调用了3次 sleep ,但整个程序基本上在2秒内就运行结束了。简单的解释,就这是这个版本的 sleep 并不能阻止 event loop 继续运行。
那么问题是,有没有好的办法阻塞 event loop 呢?
Yes,有人写了 atomic-sleep 模块:
作者对它的介绍是:
⏱️Zero CPU overhead, zero dependency, true event-loop blocking sleep ⏱️
即, 零 CPU 开销、零依赖、真正阻塞event loop的sleep 。
将上面代码中的 sleep 替换成 atomic-sleep 中的实现。
const sleep = require("atomic-sleep");
[1, 2, 3].map(async v => {
console.log(new Date(), "sleep", v, "start");
// 不需要加 await 关键字
sleep(2000);
console.log(new Date(), "sleep", v, "over");
我们看看打印了什么:
可以看到,整个过程用了6秒, 1 在 sleep 的过程中, 2 和 3 被阻塞了。
源码解释
打开 atomic-sleep 模块的源码,在 index.js 文件中,我们看到其主要逻辑是依赖了 Atomics.wait 方法,该方法会监听一个 Int32Array 对象的给定下标下的值,若值未发生改变,则一直等待(阻塞event loop),直到发生超时(由ms参数决定定):
const nil = new Int32Array(new SharedArrayBuffer(4))
function sleep (ms) {
// 参数校验相关代码略去
Atomics.wait(nil, 0, 0, Number(ms))
另外,模块还对低版本的 js 运行时做了兼容,如果不支持 Atomics ,则改用一个占用CPU运行的方式:
function sleep(ms) {
// 参数校验相关代码略去
const target = Date.now() + Number(ms);
while (target > Date.now()) {}
文章开头有说到, 某些情况下,我们需要让程序休眠一段时间再继续运行 。我提供一个具体的例子。
在写爬虫时,为了提升效率,我们通常会并发爬取一组网页。如果某个网页的请求返回了 429 异常码(Too Many Requests)。则休眠X秒再重试。我们已经知道基于 setTimeout 的休眠并不能阻止其它异步函数的执行(重试),这时 atomic-sleep 就派上用场了。
大致的爬取代码类似下面这样:
// 一组url
const urls = ["x", "y", "z", "..."];
// 并发爬取
const all_promised = urls.map(crawl);
// 获取爬取结果
const results = await Promise.all(all_promised);
// 爬取给定url
async function crawl(url) {
const res = await fetch(url);
if (res.status === 429) {
// 发现异常,休眠重试
await sleep(2000);
// 先不考虑无限重试