async-semaについて

JavaScript, Async
2020-09-10

JavaやC++などではマルチスレッドプログラミングではセマフォを使った実装はよくありますが、JavaScriptでは以下のようなケースでセマフォを利用したくなることがあります

  • XHRで一度にデータを沢山読み込みたいが呼び出し先にAPIのリクエスト制限があったり、サーバに負荷をかけたくない場合に一定の数だけリクエストする場合
  • (node.jsで)ファイルやDBのデータに非同期でアクセスする際にpause/resumeを利用したい場合
  • jsはマルチスレッド言語ではないが、webworkerを利用して処理をしたい場合

async-sema はJavaScriptのasync-awaitでセマフォを実装したライブラリです。

Usage

以下の例は20個分の要素(データ)を最大3つずつ同時に処理する例です

const { Sema } = require("async-sema");

const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));

async function main() {
  const items = [];

  for (let i = 0; i < 20; i++) items.push(i + 1);

  const s = new Sema(3);
  await Promise.all(
    items.map(async (elem) => {
      await s.acquire();
      console.log(elem, s.nrWaiting());
      await wait();
      s.release();
    })
  );
  console.log("done");
}

main();

要点は3つで

  • new Sema() でセマフォを定義。引数は同時に処理する数を指定する
  • s.acquire() でセマフォからトークンを取得。利用可能なリソースを減らす(デクリメント(P操作))する。
  • s.release() でセマフォを開放し、利用可能なリソースを増やす(インクリメント(V操作))

全体をPromise.allでラップします。

s.nrWaiting() で残りの処理数を出力します。

その他のオプション

コンストラクタで用意されている引数のオプションには以下の物があります

  • initFn セマフォのトークンを管理する場合に利用します。デフォルトではトークは'1'(() ⇒ '1')に設定されています。
  • pauseFn リクエストを一時的に中断する関数を指定することができます
  • resumeFn リクエストを再開する関数を指定することができます
  • capacity セマフォに割り当てる処理のリストがいくつあるかを指定します。通常はパフォーマンス向上のため利用される

メソッド

  • drain() セマフォをドレインし、初期化されたすべてのトークを配列で返します。プロセスを終了する前など、保留中の非同期タスクがないかを確認する事ができます。
  • tryAcquire() トークンが利用可能かを確認する、利用できない場合はundefinedが返る。
Written by Kyohei Tsukuda who lives and works in Tokyo 🇯🇵 , building useful things 🔧.