• 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;