function_call.js

- Jump To … +
  index.js backoff.js function_call.js exponential.js fibonacci.js strategy.js

- # function_call.js

- ¶

       Copyright (c) 2012 Mathieu Turcotte
       Licensed under the MIT license.

      var events = require('events');
      var precond = require('precond');
      var util = require('util');

      var Backoff = require('./backoff');
      var FibonacciBackoffStrategy = require('./strategy/fibonacci');

- ¶

  Wraps a function to be called in a backoff loop.

      function FunctionCall(fn, args, callback) {
          events.EventEmitter.call(this);

          precond.checkIsFunction(fn, 'Expected fn to be a function.');
          precond.checkIsArray(args, 'Expected args to be an array.');
          precond.checkIsFunction(callback, 'Expected callback to be a function.');

          this.function_ = fn;
          this.arguments_ = args;
          this.callback_ = callback;
          this.lastResult_ = [];
          this.numRetries_ = 0;

          this.backoff_ = null;
          this.strategy_ = null;
          this.failAfter_ = -1;

          this.state_ = FunctionCall.State_.PENDING;
      }
      util.inherits(FunctionCall, events.EventEmitter);

- ¶

  States in which the call can be.

      FunctionCall.State_ = {

- ¶

  Call isn’t started yet.

          PENDING: 0,

- ¶

  Call is in progress.

          RUNNING: 1,

- ¶

  Call completed successfully which means that either the wrapped function returned successfully or the maximal number of backoffs was reached.

          COMPLETED: 2,

- ¶

  The call was aborted.

          ABORTED: 3
      };

- ¶

  Checks whether the call is pending.

      FunctionCall.prototype.isPending = function() {
          return this.state_ == FunctionCall.State_.PENDING;
      };

- ¶

  Checks whether the call is in progress.

      FunctionCall.prototype.isRunning = function() {
          return this.state_ == FunctionCall.State_.RUNNING;
      };

- ¶

  Checks whether the call is completed.

      FunctionCall.prototype.isCompleted = function() {
          return this.state_ == FunctionCall.State_.COMPLETED;
      };

- ¶

  Checks whether the call is aborted.

      FunctionCall.prototype.isAborted = function() {
          return this.state_ == FunctionCall.State_.ABORTED;
      };

- ¶

  Sets the backoff strategy to use. Can only be called before the call is started otherwise an exception will be thrown.

      FunctionCall.prototype.setStrategy = function(strategy) {
          precond.checkState(this.isPending(), 'FunctionCall in progress.');
          this.strategy_ = strategy;
          return this; // Return this for chaining.
      };

- ¶

  Returns all intermediary results returned by the wrapped function since the initial call.

      FunctionCall.prototype.getLastResult = function() {
          return this.lastResult_.concat();
      };

- ¶

  Returns the number of times the wrapped function call was retried.

      FunctionCall.prototype.getNumRetries = function() {
          return this.numRetries_;
      };

- ¶

  Sets the backoff limit.

      FunctionCall.prototype.failAfter = function(maxNumberOfRetry) {
          precond.checkState(this.isPending(), 'FunctionCall in progress.');
          this.failAfter_ = maxNumberOfRetry;
          return this; // Return this for chaining.
      };

- ¶

  Aborts the call.

      FunctionCall.prototype.abort = function() {
          precond.checkState(!this.isCompleted(), 'FunctionCall already completed.');

          if (this.isRunning()) {
              this.backoff_.reset();
          }

          this.state_ = FunctionCall.State_.ABORTED;
      };

- ¶

  Initiates the call to the wrapped function. Accepts an optional factory function used to create the backoff instance; used when testing.

      FunctionCall.prototype.start = function(backoffFactory) {
          precond.checkState(!this.isAborted(), 'FunctionCall aborted.');
          precond.checkState(this.isPending(), 'FunctionCall already started.');

          var strategy = this.strategy_ || new FibonacciBackoffStrategy();

          this.backoff_ = backoffFactory ?
              backoffFactory(strategy) :
              new Backoff(strategy);

          this.backoff_.on('ready', this.doCall_.bind(this, true /* isRetry */));
          this.backoff_.on('fail', this.doCallback_.bind(this));
          this.backoff_.on('backoff', this.handleBackoff_.bind(this));

          if (this.failAfter_ > 0) {
              this.backoff_.failAfter(this.failAfter_);
          }

          this.state_ = FunctionCall.State_.RUNNING;
          this.doCall_(false /* isRetry */);
      };

- ¶

  Calls the wrapped function.

      FunctionCall.prototype.doCall_ = function(isRetry) {
          if (isRetry) {
              this.numRetries_++;
          }
          var eventArgs = ['call'].concat(this.arguments_);
          events.EventEmitter.prototype.emit.apply(this, eventArgs);
          var callback = this.handleFunctionCallback_.bind(this);
          this.function_.apply(null, this.arguments_.concat(callback));
      };

- ¶

  Calls the wrapped function’s callback with the last result returned by the wrapped function.

      FunctionCall.prototype.doCallback_ = function() {
          this.callback_.apply(null, this.lastResult_);
      };

- ¶

  Handles wrapped function’s completion. This method acts as a replacement for the original callback function.

      FunctionCall.prototype.handleFunctionCallback_ = function() {
          if (this.isAborted()) {
              return;
          }

          var args = Array.prototype.slice.call(arguments);
          this.lastResult_ = args; // Save last callback arguments.
          events.EventEmitter.prototype.emit.apply(this, ['callback'].concat(args));

          if (args[0]) {
              this.backoff_.backoff(args[0]);
          } else {
              this.state_ = FunctionCall.State_.COMPLETED;
              this.doCallback_();
          }
      };

- ¶

  Handles the backoff event by reemitting it.

      FunctionCall.prototype.handleBackoff_ = function(number, delay, err) {
          this.emit('backoff', number, delay, err);
      };

      module.exports = FunctionCall;