@fantastic-utils/memo
Version:
powerful memorization lib, which inspired by memoize-one, proxy-memoize
427 lines (348 loc) • 12.8 kB
JavaScript
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import { getDataType } from '@fantastic-utils/data-type';
var ORIGINAL_SYMBOL = Symbol('_original');
var AFFECTED_TYPES;
(function (AFFECTED_TYPES) {
AFFECTED_TYPES["KEYS"] = "k";
AFFECTED_TYPES["HAS_KEY"] = "h";
AFFECTED_TYPES["HAS_OWN_KEY"] = "o";
AFFECTED_TYPES["ALL_KEYS"] = "a";
})(AFFECTED_TYPES || (AFFECTED_TYPES = {}));
var defaultShouldCompare = function defaultShouldCompare() {
return true;
}; // get object prototype
var getProto = Object.getPrototypeOf; // Properties that are both non-configurable and non-writable will break
// the proxy get trap when we try to return a recursive/child compare proxy
// from them. We can avoid this by making a copy of the target object with
// all descriptors marked as configurable, see `copyTargetObject`.
var needsToCopyTargetObject = function needsToCopyTargetObject(obj) {
return Object.values(Object.getOwnPropertyDescriptors(obj)).some(function (descriptor) {
return !descriptor.configurable && !descriptor.writable;
});
}; // Make a copy with all descriptors marked as configurable.
var copyTargetObject = function copyTargetObject(obj) {
if (Array.isArray(obj)) {
// Arrays need a special way to copy
return Array.from(obj);
} // For non-array objects, we create a new object keeping the prototype
// with changing all configurable options (otherwise, proxies will complain)
var descriptors = Object.getOwnPropertyDescriptors(obj);
Object.values(descriptors).forEach(function (desc) {
desc.configurable = true;
});
return Object.create(getProto(obj), descriptors);
};
export var original = function original(proxyTarget) {
return proxyTarget[ORIGINAL_SYMBOL];
};
export var getUsed = function getUsed(arg, affected) {
var used = affected.get(arg);
if (!used) {
var _used;
used = (_used = {}, _defineProperty(_used, AFFECTED_TYPES.KEYS, new Map()), _defineProperty(_used, AFFECTED_TYPES.HAS_KEY, new Map()), _defineProperty(_used, AFFECTED_TYPES.ALL_KEYS, [false, []]), _defineProperty(_used, AFFECTED_TYPES.HAS_OWN_KEY, new Map()), _used);
affected.set(arg, used);
}
return used;
};
var createHandler = function createHandler(arg, memoCfg, affected) {
var handler = {
get: function get(target, key) {
if (key === ORIGINAL_SYMBOL) return arg;
var keyType = getDataType(key);
var reflectValue = Reflect.get(target, key);
if (keyType === 'Symbol') return reflectValue;
var used = getUsed(arg, affected);
var affectedKeysMap = used[AFFECTED_TYPES.KEYS];
var affectedKeyCfg = affectedKeysMap.get(key);
if (affectedKeyCfg) {
return affectedKeyCfg.v;
}
var normalizedArgCfg = normalizeArgs(reflectValue, memoCfg, affected);
affectedKeysMap.set(key, normalizedArgCfg);
return normalizedArgCfg.v;
},
has: function has(target, key) {
var reflectHas = Reflect.has(target, key);
var used = getUsed(arg, affected);
var affectedHasKeysMap = used[AFFECTED_TYPES.HAS_KEY];
var affectedHasKeyCfg = affectedHasKeysMap.get(key);
if (affectedHasKeyCfg) {
return affectedHasKeyCfg.v;
}
affectedHasKeysMap.set(key, {
v: reflectHas
});
return reflectHas;
},
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, key) {
var desc = Object.getOwnPropertyDescriptor(target, key);
var used = getUsed(arg, affected);
var affectedHasOwnKey = used[AFFECTED_TYPES.HAS_OWN_KEY];
var affectedHasOwnKeyCfg = affectedHasOwnKey.get(key);
if (!affectedHasOwnKeyCfg) {
affectedHasOwnKey.set(key, {
v: !!desc
});
}
return desc;
},
ownKeys: function ownKeys(target) {
var ownKeys = Reflect.ownKeys(target);
var used = getUsed(arg, affected);
var _used$AFFECTED_TYPES$ = _slicedToArray(used[AFFECTED_TYPES.ALL_KEYS], 1),
allKeysCalled = _used$AFFECTED_TYPES$[0];
if (!allKeysCalled) {
used[AFFECTED_TYPES.ALL_KEYS] = [true, ownKeys];
}
return ownKeys;
}
};
return handler;
};
var createProxy = function createProxy(arg, memoCfg, affected) {
var handler = createHandler(arg, memoCfg, affected);
return new Proxy(arg, handler);
};
var normalizeArgs = function normalizeArgs(arg, memoCfg, affected) {
var dataType = getDataType(arg);
switch (dataType) {
case 'Object':
case 'Array':
var used = getUsed(arg, affected);
var selfProxy = used === null || used === void 0 ? void 0 : used.p;
var needCopy = needsToCopyTargetObject(arg);
var proxyValue = selfProxy;
var plainArg = arg;
if (!proxyValue) {
if (needCopy) {
plainArg = copyTargetObject(arg);
}
proxyValue = createProxy(plainArg, memoCfg, affected);
used.p = proxyValue;
}
return {
t: dataType,
r: needCopy ? plainArg : arg,
v: proxyValue,
a: affected
};
default:
return {
t: dataType,
r: arg,
v: arg,
a: affected
};
}
};
var isChanged = function isChanged(normalizedArg, newArg, memoCfg) {
var t = normalizedArg.t,
r = normalizedArg.r,
affected = normalizedArg.a;
var objectShallowCompare = memoCfg.objectShallowCompare;
if (objectShallowCompare) {
return !Object.is(r, newArg);
}
var newArgType = getDataType(newArg);
switch (t) {
case 'Array':
case 'Object':
var used = affected.get(r);
if (newArgType !== t || !used) {
return true;
}
var affectedKeys = used[AFFECTED_TYPES.KEYS];
var affectedHasKey = used[AFFECTED_TYPES.HAS_KEY];
var affectedHasOwnKey = used[AFFECTED_TYPES.HAS_OWN_KEY];
var _used$AFFECTED_TYPES$2 = _slicedToArray(used[AFFECTED_TYPES.ALL_KEYS], 2),
allKeysCalled = _used$AFFECTED_TYPES$2[0],
keys = _used$AFFECTED_TYPES$2[1];
var changed = false;
var _iterator = _createForOfIteratorHelper(affectedHasKey || []),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _step$value = _slicedToArray(_step.value, 2),
_key = _step$value[0],
hasKeyCfg = _step$value[1];
changed = hasKeyCfg.v !== Reflect.has(newArg, _key);
if (changed) break;
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
if (changed) return true;
if (allKeysCalled) {
var newArgOwnKeys = Reflect.ownKeys(newArg);
changed = keys.length !== newArgOwnKeys.length || keys.some(function (k, i) {
return k !== newArgOwnKeys[i];
});
if (changed) return changed;
} else {
var _iterator2 = _createForOfIteratorHelper(affectedHasOwnKey || []),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _step2$value = _slicedToArray(_step2.value, 2),
key = _step2$value[0],
hasOwnKeyCfg = _step2$value[1];
changed = hasOwnKeyCfg.v !== !!Reflect.getOwnPropertyDescriptor(newArg, key);
if (changed) break;
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
}
if (changed) return true;
var _iterator3 = _createForOfIteratorHelper(affectedKeys || []),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var _step3$value = _slicedToArray(_step3.value, 2),
_key2 = _step3$value[0],
affectedKeyArg = _step3$value[1];
changed = isChanged(affectedKeyArg, newArg[_key2], memoCfg);
if (changed) break;
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
if (changed) return true;
return false;
default:
return !Object.is(r, newArg);
}
};
var isArgsChanged = function isArgsChanged(cachedProxyArgCfg, newArgs, memoCfg) {
var shouldCompare = memoCfg.shouldCompare;
if (shouldCompare && !shouldCompare(newArgs, cachedProxyArgCfg)) {
return false;
}
var argLength = cachedProxyArgCfg.length;
if (argLength !== newArgs.length) {
return true;
}
var changed = false;
for (var i = 0; i < argLength; i++) {
changed = isChanged(cachedProxyArgCfg[i], newArgs[i], memoCfg);
if (changed) break;
}
return changed;
};
var getCachedProxyArgCfg = function getCachedProxyArgCfg(args, memoCfg) {
var proxyArgs = [];
var cachedProxyArgsCfg = args.reduce(function (prev, arg) {
var affected = new WeakMap();
var normalizedArg = normalizeArgs(arg, memoCfg, affected);
prev.push(normalizedArg);
proxyArgs.push(normalizedArg.v);
return prev;
}, []);
return [proxyArgs, cachedProxyArgsCfg];
};
var untrack = function untrack(x, seen) {
if (['Object', 'Array'].indexOf(getDataType(x)) === -1) return x;
var untrackedObj = original(x);
if (untrackedObj) {
return untrackedObj;
}
if (!seen.has(x)) {
seen.add(x);
Object.entries(x).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
k = _ref2[0],
v = _ref2[1];
var vv = untrack(v, seen);
if (!Object.is(vv, v)) x[k] = vv;
});
}
return x;
};
export var memo = function memo(fn) {
var memoCfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
objectShallowCompare: false,
shouldCompare: defaultShouldCompare
};
var customIsChanged = memoCfg.isChanged;
var cachedProxyArgsCfg = [];
var cachedResult;
return function () {
var proxyArgs = [];
for (var _len = arguments.length, args = new Array(_len), _key3 = 0; _key3 < _len; _key3++) {
args[_key3] = arguments[_key3];
}
var changed = customIsChanged ? customIsChanged(args, cachedProxyArgsCfg) : isArgsChanged(cachedProxyArgsCfg, args, memoCfg);
if (!changed) {
return cachedResult;
}
var _getCachedProxyArgCfg = getCachedProxyArgCfg(args, memoCfg);
var _getCachedProxyArgCfg2 = _slicedToArray(_getCachedProxyArgCfg, 2);
proxyArgs = _getCachedProxyArgCfg2[0];
cachedProxyArgsCfg = _getCachedProxyArgCfg2[1];
var rt = fn.apply(void 0, _toConsumableArray(proxyArgs));
cachedResult = untrack(rt, new Set());
return cachedResult;
};
};
export var memoAsync = function memoAsync(fn) {
var memoCfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
objectShallowCompare: false,
shouldCompare: defaultShouldCompare
};
var customIsChanged = memoCfg.isChanged;
var cachedProxyArgsCfg = [];
var cachedResult;
return /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var proxyArgs,
_len2,
args,
_key4,
changed,
_getCachedProxyArgCfg3,
_getCachedProxyArgCfg4,
rt,
_args = arguments;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
proxyArgs = [];
for (_len2 = _args.length, args = new Array(_len2), _key4 = 0; _key4 < _len2; _key4++) {
args[_key4] = _args[_key4];
}
changed = customIsChanged ? customIsChanged(args, cachedProxyArgsCfg) : isArgsChanged(cachedProxyArgsCfg, args, memoCfg);
if (changed) {
_context.next = 5;
break;
}
return _context.abrupt("return", cachedResult);
case 5:
_getCachedProxyArgCfg3 = getCachedProxyArgCfg(args, memoCfg);
_getCachedProxyArgCfg4 = _slicedToArray(_getCachedProxyArgCfg3, 2);
proxyArgs = _getCachedProxyArgCfg4[0];
cachedProxyArgsCfg = _getCachedProxyArgCfg4[1];
_context.next = 11;
return fn.apply(void 0, _toConsumableArray(proxyArgs));
case 11:
rt = _context.sent;
cachedResult = untrack(rt, new Set());
return _context.abrupt("return", cachedResult);
case 14:
case "end":
return _context.stop();
}
}
}, _callee);
}));
};