Skip to content

once / singleFlight

何かを きっかり一度だけ 実行し、その結果を使い回します。設定の解析を遅らせて初期化するとき、重たい一度きりの計算、遅延ゲッターなどに使います。once は同期で、singleFlight はその非同期版です。

API

once(fn)

パラメーター説明既定値
fn一度だけ実行する関数。関数でない値はそのまま返しますFunction | any必須

包まれた関数を返します。最初の呼び出しで評価して覚え、以後の呼び出しは、どんな引数を渡しても その最初の結果を返します。

singleFlight(fn)

パラメーター説明既定値
fn一度だけ実行する非同期関数() => Promise<T>必須

包まれた関数に加えて、次のものを返します。

メンバー説明
reset()覚えた結果を捨て、次の呼び出しでもう一度 fn を走らせます() => void
started結果を持っているか、いま走っている最中かどうかboolean

memoize(fn)

once の別名で、非推奨です。下の注意を読んでください。

使用例

js
import { once, singleFlight } from 'ranuts';

// 同期:設定は最初に触れたときだけ解析します
const config = once(() => JSON.parse(rawConfig));
config(); // 解析します
config(); // 覚えた結果を返します

// 非同期:呼び出しがいくつ競り合っても、データベースを開くのは一度だけです
const ready = singleFlight(() => db.openDataBase());
await Promise.all([ready(), ready(), ready()]); // 開くのは一度

補足

  1. once は引数ごとに覚えるわけではありません。 効くのは最初の呼び出しの引数だけです。引数ごとに覚えたいなら、自分で Map を使ってください。
  2. once は評価が済むと fn を手放します。 ですから、その関数が抱え込んでいたものはガベージコレクトされます。
  3. singleFlight は失敗を覚えません。 失敗した試みはキャッシュを消すので、一時的なネットワークの不調はやり直せます。reject された Promise を覚えてしまうと、一度のつまずきが永久のものになります。
  4. 同時に呼んだ singleFlight は、走っている最中の Promise を共有します。 ですから N 個の呼び出しが競り合っても fn は一度しか走りません。これは「init() が void を返すので呼び出し側が await できず、早すぎる書き込みが失敗する」という、よくある不具合への答えです。

0.3 で名前を変えました

memoize は誤解を招く名前でした。引数ごとに覚えたことなど一度もなく、ただ一度だけ走らせていただけです。いまは once の別名で、非推奨です。型の宣言も以前は誤っていました(引数を素通しするのに、引数なしと宣言していました)。いまは fn から推論します。

MIT ライセンスのもとで公開されています。