UNPKG

sqmicro-commons

Version:

Commons for SQ analytics microservices.

127 lines (109 loc) 3.82 kB
/** * Дроссель. Микс-ин. Позволяет дополнить классы возможностью игнорировать вызовы * заданных методов с заданной вероятностью. * Дросселированные методы возвращают ThrottledResult. * * @typedef {object} ThrottledResult * @property {boolean} isThrottled true - вызов был проигнорирован (в этом случае * value === null). * @property {*} value Результат действительного вызова (если isThrottled === * false) или null */ // Skip none. const DEFAULT_THROTTLE_LEVEL = 0; const Private = Symbol('Private'); // Подменяет заданные методы их дросселированными версиями. const proxyHandler = { get(target, name) { return target[Private].throttledMembers.indexOf(name) >= 0 ? throttle(target, name) : target[name]; } }; module.exports = Base => class Throttled extends Base{ /** * Получить уровень дросселирования (вероятность с которой вызовы будут * игнорироваться). По умолчанию - 0. */ get throttleLevel() { return this[Private].throttleLevel; } /** * Задать уровень дросселирования - положительное число или ноль. */ set throttleLevel(value) { if (typeof value !== 'number' || value < 0) { throw Error('Throttle level should be a positive number or zero.'); } this[Private].throttleLevel = value; } /** * Задать список дросселируемых методов (массив строк). */ set throttledMembers(members) { if (typeof members === 'string') { members = [members]; } if (!isArrayOfStrings(members)) { throw TypeError('Throttled members should be a string or an array of strings'); } this[Private].throttledMembers = members; } constructor(...args) { super(...args); this[Private] = {}; this.throttleLevel = DEFAULT_THROTTLE_LEVEL; this.throttledMembers = []; return new Proxy(this, proxyHandler); } }; /** * Вернуть дросселированную версию метода целевого объекта. * @inner * @param {object} target Целевой объект. * @param {string} name Имя метода. */ function throttle(target, name) { let qlue = getClue(); return (...args) => qlue < target.throttleLevel ? throttledResult() : passedResult(target[name].apply(target, args)); } /** * Создать результат пропуска вызова метода * @returns {ThrottledResult} */ function throttledResult() { return { throttled: true, value: null }; } /** * Создать результат действительного вызова дросселированного метода. * @param {*} value Результат действительного вызова. * @returns {ThrottledResult} */ function passedResult(value) { return { throttled: false, value }; } function isArrayOfStrings(arr) { if (!Array.isArray(arr)) { return false; } for (let item of arr) { if (typeof item !== 'string') { return false; } } return true; } /** * Return a number in a non-inclusive range (0, 100) with a constant probability * density. * @inner */ function getClue() { return Number.EPSILON + (100 - Number.EPSILON) * Math.random(); }