UNPKG

tuf-resil

Version:

Functional ES6 Resiliency: Circuit Breaker, Fallback, Retry, Backoff, Rate Limiting

41 lines (39 loc) 2.56 kB
const slep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const to = (p) => p.then(data => [null, data]).catch(err => [err, null]); export default (props) => { props = {retries: 3, backoff: 1000, rate: 0, failThreshold: 10, successThreshold: 3, breakerTimeout: 10000, backoffFunction : (delay,initial,attempt) => delay+initial, retryBreaker: false, ...props} let [breakerState, successes, failures, nextAttempt, lastCall] = [2,0,0,Date.now(),0]; return (asyncErrorHandler, asyncFallback, asyncFunction) => async (...params) => { let [tries, delay] = [props.retries, 0]; while(tries-->-1){ if(breakerState===0 && Date.now() > nextAttempt) breakerState=1; if(breakerState===0) { if(asyncErrorHandler) await asyncErrorHandler({error: new Error('Breaker Closed'), breakerState, failures, successes, tries: props.retries-tries, props}); if(props.retryBreaker) await slep(delay = props.backoffFunction(delay,props.backoff,(props.retries-tries))); else return asyncFallback ? await asyncFallback(...params) : null; }else{ if(props.rate>0){ const rate = lastCall ? Date.now() - lastCall : props.rate; lastCall = Date.now() + (rate < props.rate ? props.rate - rate : 0); if(rate < props.rate ) await slep(props.rate - rate); } let [err, res] = await to(asyncFunction(...params)); if(res || err===null) { if(breakerState===1 && ++successes >= props.successThreshold){ successes=failures=0; breakerState=2; } return res; } if(++failures >= props.failThreshold) { successes=breakerState=0; nextAttempt = Date.now() + props.breakerTimeout; } if(asyncErrorHandler) await asyncErrorHandler({error: err, breakerState, failures, successes, tries: props.retries-tries, props}); if(props.backoffFunction) await slep(delay = props.backoffFunction(delay,props.backoff,(props.retries-tries))); } } if(asyncErrorHandler) await asyncErrorHandler({error: new Error('Out of retries'), breakerState, failures, successes, tries: props.retries-tries, props}); return asyncFallback ? await asyncFallback(...params) : null; } };