once / singleFlight

只执行一次并复用结果,用于惰性初始化:配置解析、昂贵的一次性计算、懒 getter。 once 是同步版,singleFlight 是异步版。

API

once(fn)

参数 说明 类型 默认值
fn 只执行一次的函数;传非函数值则原样返回该值 Function | any 必填

返回包装后的函数。首次调用求值并缓存,之后无论传什么参数都直接返回首次的结果。

singleFlight(fn)

参数 说明 类型 默认值
fn 只执行一次的异步函数 () => Promise<T> 必填

返回包装后的函数,并附带:

成员 说明 类型
reset() 丢弃缓存结果,下次调用重新执行 fn () => void
started 是否已有结果或正在执行中 boolean

memoize(fn)

once 的废弃别名,见下方说明。

示例

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 不缓存失败。失败会清空缓存,让一次偶发的网络抖动仍可重试:缓存一个 rejected promise 会把偶发故障变成永久故障。
  4. 并发调用共享同一个在途 promise,N 个调用方竞争时 fn 也只跑一次。这正是「init() 返回 void → 调用方无从 await → 早期写操作全部失败」这个经典 bug 的解法。