2023年2月19日日曜日

いまさら聞けないAsync Hooks

 Node.js v8あたりで追加されたAsync Hooksについて。タイトル通りいまさらなんですが、簡単な説明をします。

あまり詳しい説明はしません。

簡単な説明

名前の通り、非同期処理にフックを仕掛けるものです。asyncと名前がついていますが、async-awaitだけでなくPromiseやコールバックなど、非同期処理全般にフックを仕掛けられます。

もうちょっと詳しく

Node.jsの非同期処理は、Promiseもasync-awaitも内部的にコールバック関数が使われますが、このコールバック関数が

  • init=登録されたとき(1回)
  • before=呼び出される前(0回以上)
  • after=呼び出された後(0回以上)
  • destroy=破棄されたとき(1回)

にそれぞれ呼び出される関数を指定できます。

たとえばsetInterval()は以下のように使いますが、

const timeout = setInterval(() => {
  // コールバック関数
}, 1000);

// タイマーが不要になったら登録解除
clearInterval(timeout);

これを先程の説明に当てはめると、コールバック関数が

  • 登録されたとき=setInterval()が呼ばれたとき
  • 呼び出される前=実行される前
  • 呼び出された後=実行された後
  • 破棄されたとき=clearInterval()が呼ばれたとき

に該当し、それぞれのタイミングでフックを仕掛けられます。

正確に言うと、破棄されるのはclearInterval()が呼ばれてすぐではなく、ガベコレとかでコールバック関数自体が破棄されたタイミングです。

もうちょっとだけ詳しく

具体的な使い方はドキュメントを見ていただいたきのですが、すべての非同期処理には一意のID(async ID)が割り当てられており、フック関数が登録されたときには「新しい非同期処理のasync ID」と「呼び出し元の非同期処理のasync ID」が取得できます。

使いみち

一番よく使われる使い方が、ウェブサーバーを作り、リクエストごとに一意なストレージを用意するというものです。いわゆるスレッドローカルストレージ的なやつです。というかそれ以外の用途が思いつきません

実現方法もあまり難しくはなく、

  • グローバル変数として、オブジェクトやMapを用意しておく
  • 最初にリクエストを受けたときに、用意したオブジェクトにasync IDをキーとした要素を新しく作る
  • 新しい非同期処理が作られるたびに、新しいasync IDをキーとした要素を作る。中身は呼び出し元のasync IDの要素をコピーする
  • ストレージを操作するときは、現在のasync IDをキーとして要素を操作する
  • 非同期処理が破棄されたら(destroy)、オブジェクトからasync IDのキーを削除する

こんな感じです。Express.jsとかで専用のミドルウェアを作るのも多分そんなに難しくはありません。

注意

これを使うとリクエストオブジェクトを毎回引き回さずに済むので便利なんですが、非同期処理が使われるたびにフック関数が実行されるのでパフォーマンスには要注意です。

Node.jsは非同期処理がポコポコ使われるので、ちょっとでも時間がかかる処理をすると全体のパフォーマンスに深刻な影響が出ます。

というか、ローカルストレージの用途にはv13で追加されたAsyncLocalStorageを使いましょう。多分開発陣もパフォーマンスの問題を懸念したんでしょう。

0 件のコメント:

コメントを投稿