120 lines
4.1 KiB
JavaScript
120 lines
4.1 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.asyncMemoizer = void 0;
|
|
const lru_cache_1 = require("lru-cache");
|
|
const events_1 = require("events");
|
|
const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
|
|
const freeze_1 = require("./freeze");
|
|
const sync_1 = require("./sync");
|
|
function asyncMemoizer(options) {
|
|
const cache = new lru_cache_1.LRUCache(options);
|
|
const load = options.load;
|
|
const hash = options.hash;
|
|
const bypass = options.bypass;
|
|
const itemTTL = options.itemTTL;
|
|
const freeze = options.freeze;
|
|
const clone = options.clone;
|
|
const queueTTL = options.queueTTL || 1000;
|
|
const loading = new Map();
|
|
const emitter = new events_1.EventEmitter();
|
|
const memoizerMethods = Object.assign({
|
|
del,
|
|
reset: () => cache.clear(),
|
|
keys: () => [...cache.keys()],
|
|
on: emitter.on.bind(emitter),
|
|
once: emitter.once.bind(emitter)
|
|
}, options);
|
|
if (options.disable) {
|
|
return Object.assign(load, memoizerMethods);
|
|
}
|
|
function del(...args) {
|
|
const key = hash(...args);
|
|
cache.delete(key);
|
|
}
|
|
function add(key, parameters, result) {
|
|
if (freeze) {
|
|
result.forEach(freeze_1.deepFreeze);
|
|
}
|
|
if (itemTTL) {
|
|
cache.set(key, result, { ttl: itemTTL(...parameters.concat(result)) });
|
|
}
|
|
else {
|
|
cache.set(key, result);
|
|
}
|
|
}
|
|
function runCallbacks(callbacks, args) {
|
|
for (const callback of callbacks) {
|
|
// Simulate async call when returning from cache
|
|
// and yield between callback resolution
|
|
if (clone) {
|
|
setImmediate(callback, ...args.map(lodash_clonedeep_1.default));
|
|
}
|
|
else {
|
|
setImmediate(callback, ...args);
|
|
}
|
|
}
|
|
}
|
|
function emit(event, ...parameters) {
|
|
emitter.emit(event, ...parameters);
|
|
}
|
|
function memoizedFunction(...args) {
|
|
const parameters = args.slice(0, -1);
|
|
const callback = args.slice(-1).pop();
|
|
let key;
|
|
if (bypass && bypass(...parameters)) {
|
|
emit('miss', ...parameters);
|
|
return load(...args);
|
|
}
|
|
if (parameters.length === 0 && !hash) {
|
|
//the load function only receives callback.
|
|
key = '_';
|
|
}
|
|
else {
|
|
key = hash(...parameters);
|
|
}
|
|
const fromCache = cache.get(key);
|
|
if (fromCache) {
|
|
emit('hit', ...parameters);
|
|
// found, invoke callback
|
|
return runCallbacks([callback], [null].concat(fromCache));
|
|
}
|
|
const pendingLoad = loading.get(key);
|
|
if (pendingLoad && pendingLoad.expiresAt > Date.now()) {
|
|
// request already in progress, queue and return
|
|
pendingLoad.queue.push(callback);
|
|
emit('queue', ...parameters);
|
|
return;
|
|
}
|
|
emit('miss', ...parameters);
|
|
const started = Date.now();
|
|
// no pending request or not resolved before expiration
|
|
// create a new queue and invoke load
|
|
const queue = [callback];
|
|
loading.set(key, {
|
|
queue,
|
|
expiresAt: started + queueTTL
|
|
});
|
|
const loadHandler = (...args) => {
|
|
const err = args[0];
|
|
if (!err) {
|
|
add(key, parameters, args.slice(1));
|
|
}
|
|
// this can potentially delete a different queue than `queue` if
|
|
// this callback was called after expiration.
|
|
// that will only cause a new call to be performed and a new queue to be
|
|
// created
|
|
loading.delete(key);
|
|
emit('loaded', Date.now() - started, ...parameters);
|
|
runCallbacks(queue, args);
|
|
};
|
|
load(...parameters, loadHandler);
|
|
}
|
|
;
|
|
return Object.assign(memoizedFunction, memoizerMethods);
|
|
}
|
|
exports.asyncMemoizer = asyncMemoizer;
|
|
asyncMemoizer.sync = sync_1.syncMemoizer;
|
|
//# sourceMappingURL=async.js.map
|