sqmicro-commons
Version:
Commons for SQ analytics microservices.
127 lines (109 loc) • 3.82 kB
JavaScript
/**
* Дроссель. Микс-ин. Позволяет дополнить классы возможностью игнорировать вызовы
* заданных методов с заданной вероятностью.
* Дросселированные методы возвращают 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();
}