once / singleFlight
只执行一次并复用结果 —— 用于惰性初始化:配置解析、昂贵的一次性计算、懒 getter。 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不缓存失败。失败会清空缓存,让一次偶发的网络抖动仍可重试 —— 缓存一个 rejected promise 会把偶发故障变成永久故障。- 并发调用共享同一个在途 promise,N 个调用方竞争时
fn也只跑一次。这正是 「init()返回 void → 调用方无从 await → 早期写操作全部失败」这个经典 bug 的解法。
0.3 改名
memoize 是个有误导的名字 —— 它从不按参数缓存,只是「执行一次」。现已成为 once 的别名并标记废弃。 它此前的类型签名也是错的(声明为无参却透传参数),现在从 fn 推导。