添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 What Matters
atomic-sleep: 一个名副其实的JavaScript sleep函数

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);
    // 先不考虑无限重试