All files / copacetic/lib/backoff-strategies strategy.js

100% Statements 26/26
92.85% Branches 13/14
100% Functions 7/7
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 801x 1x                           63x   63x 63x 63x 63x                     34x 34x   34x 34x   34x 39x   23x   16x   16x 5x           11x     34x     34x                 11x 1x     11x 8x     3x       1x  
const noop = require('node-noop').noop
const precond = require('precond')
 
/**
 * @class
 * @classdesc An abstract class, defining the skeleton for a backoff strategy,
 * it is expected that this will be subclassed, with an implementation of
 * _intervalFunc
 */
class BackoffStrategy {
  /**
   * @param {Integer} [retries=3] The total number of attempts a backoff strategy will perform
   * @param {Integer} [maxDelay=0] The maximum delay before retrying, 0 = no max.
   */
  constructor (retries = 3, maxDelay = 0) {
    precond.checkArgument(retries > 0, 'retries must be an integer > 0')
 
    this.retries = retries
    this.maxDelay = maxDelay
    this.func = noop
    this.lastDelay = 0
  }
 
  /**
   * starts the execution of "func", backing off at some Interval.
   * @param {Function} [func=noop] - the function to execute, must have a signature of () => Promise
   * @param {Integer} [retries=1] How many times func will be called before giving up
   * @param {Integer} [maxDelay=0] The maximum interval of time to wait when retrying
   * @return {Promise}
   */
  execute ({ func = this.func, retries = this.retries, maxDelay = this.maxDelay }) {
    this.func = func
    this.lastDelay = 0
 
    const retry = new Promise((resolve, reject) => {
      let attempt = 1
 
      const loop = () => {
        this
          .func()
          .then(res => resolve(res))
          .catch((err) => {
            attempt += 1
 
            if ((retries === 0) || (attempt <= retries)) {
              return setTimeout(
                loop,
                this._scheduleNextTry(attempt, maxDelay)
              )
            }
 
            return reject(err)
          })
      }
      loop()
    })
 
    return retry
  }
 
  /**
   * Calculates the next delay before executing func
   * @param {Integer} attempt
   * @param {Integer} maxDelay
   */
  _scheduleNextTry (attempt, maxDelay = 0) {
    if (maxDelay < 0) {
      maxDelay = 0
    }
 
    if (maxDelay === 0) {
      return this._intervalFunc(attempt)
    }
 
    return Math.min(this._intervalFunc(attempt), maxDelay)
  }
}
 
module.exports = BackoffStrategy