UNPKG

async-throttle-cache

Version:

Throttle or debounce asynchronous functions and return cached result for each function calls. It can be used for rate limit.

88 lines (86 loc) 2.41 kB
const returnSelf = (result) => Promise.resolve(result); export default function asyncThrottleCache(fn, wait = 0, { key = (...args) => JSON.stringify(args), serialize = returnSelf, deserialize = returnSelf, debounce = undefined, } = {}) { const cache = {}; const debounceLeading = debounce?.leading; return function (...args) { // eslint-disable-line func-names const cacheKey = key(...args); const cached = cache[cacheKey]; const onFinish = (index, err, result) => { const cachedF = cache[cacheKey]; if (cachedF?.i === index) { cachedF.f = true; if (err) { cachedF.e = err; cachedF.j.map((f) => f(err)); } else { serialize(result) .then((r) => { cachedF.r = r; cachedF.s.map((f, i) => deserialize(r).then(f, cachedF.j[i])); }, onFinish); } if (!cachedF.t || (debounce && !debounceLeading)) { delete cache[cacheKey]; } } return result; }; const exec = () => { const cachedE = cache[cacheKey]; const i = cachedE.i = Date.now(); cachedE.f = false; return fn.apply(this, args) .then((result) => onFinish(i, undefined, result), (err) => { onFinish(i, err); return Promise.reject(err); }); }; const timeout = () => setTimeout(() => { const cachedT = cache[cacheKey]; if (cachedT) { if (debounce && cachedT.s.length) { exec(); } else if (cachedT.f) { delete cache[cacheKey]; } else { cachedT.t = 0; } } }, wait); const padding = () => new Promise((resolve, reject) => { const cachedP = cache[cacheKey]; cachedP.s.push(resolve); cachedP.j.push(reject); }); if (cached) { if (debounce) { clearTimeout(cached.t); cached.t = timeout(); } else { const { e, // error r, // result f, // finished } = cached; if (e !== undefined) { return Promise.reject(e); } if (f) { return deserialize(r); } } return padding(); } cache[cacheKey] = { s: [], // resolve callbacks j: [], // reject callbacks t: timeout(), }; return (!debounce || debounceLeading) ? exec() : padding(); }; }