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()]); // 開くのは一度補足
onceは引数ごとに覚えるわけではありません。 効くのは最初の呼び出しの引数だけです。引数ごとに覚えたいなら、自分でMapを使ってください。onceは評価が済むとfnを手放します。 ですから、その関数が抱え込んでいたものはガベージコレクトされます。singleFlightは失敗を覚えません。 失敗した試みはキャッシュを消すので、一時的なネットワークの不調はやり直せます。reject された Promise を覚えてしまうと、一度のつまずきが永久のものになります。- 同時に呼んだ
singleFlightは、走っている最中の Promise を共有します。 ですから N 個の呼び出しが競り合ってもfnは一度しか走りません。これは「init() が void を返すので呼び出し側が await できず、早すぎる書き込みが失敗する」という、よくある不具合への答えです。
0.3 で名前を変えました
memoize は誤解を招く名前でした。引数ごとに覚えたことなど一度もなく、ただ一度だけ走らせていただけです。いまは once の別名で、非推奨です。型の宣言も以前は誤っていました(引数を素通しするのに、引数なしと宣言していました)。いまは fn から推論します。