|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
-
-
-
- var events = require('events');
- var precond = require('precond');
- var util = require('util');
-
- var Backoff = require('./backoff');
- var FibonacciBackoffStrategy = require('./strategy/fibonacci');
-
-
- 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.retryPredicate_ = FunctionCall.DEFAULT_RETRY_PREDICATE_;
-
- this.state_ = FunctionCall.State_.PENDING;
- }
- util.inherits(FunctionCall, events.EventEmitter);
-
-
- FunctionCall.State_ = {
-
- PENDING: 0,
-
- RUNNING: 1,
-
-
- COMPLETED: 2,
-
- ABORTED: 3
- };
-
-
- FunctionCall.DEFAULT_RETRY_PREDICATE_ = function(err) {
- return true;
- };
-
-
- FunctionCall.prototype.isPending = function() {
- return this.state_ == FunctionCall.State_.PENDING;
- };
-
-
- FunctionCall.prototype.isRunning = function() {
- return this.state_ == FunctionCall.State_.RUNNING;
- };
-
-
- FunctionCall.prototype.isCompleted = function() {
- return this.state_ == FunctionCall.State_.COMPLETED;
- };
-
-
- FunctionCall.prototype.isAborted = function() {
- return this.state_ == FunctionCall.State_.ABORTED;
- };
-
-
-
- FunctionCall.prototype.setStrategy = function(strategy) {
- precond.checkState(this.isPending(), 'FunctionCall in progress.');
- this.strategy_ = strategy;
- return this;
- };
-
-
-
-
-
- FunctionCall.prototype.retryIf = function(retryPredicate) {
- precond.checkState(this.isPending(), 'FunctionCall in progress.');
- this.retryPredicate_ = retryPredicate;
- return this;
- };
-
-
-
- FunctionCall.prototype.getLastResult = function() {
- return this.lastResult_.concat();
- };
-
-
- FunctionCall.prototype.getNumRetries = function() {
- return this.numRetries_;
- };
-
-
- FunctionCall.prototype.failAfter = function(maxNumberOfRetry) {
- precond.checkState(this.isPending(), 'FunctionCall in progress.');
- this.failAfter_ = maxNumberOfRetry;
- return this;
- };
-
-
- FunctionCall.prototype.abort = function() {
- if (this.isCompleted() || this.isAborted()) {
- return;
- }
-
- if (this.isRunning()) {
- this.backoff_.reset();
- }
-
- this.state_ = FunctionCall.State_.ABORTED;
- this.lastResult_ = [new Error('Backoff aborted.')];
- this.emit('abort');
- this.doCallback_();
- };
-
-
-
- FunctionCall.prototype.start = function(backoffFactory) {
- precond.checkState(!this.isAborted(), 'FunctionCall is 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 ));
- 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 );
- };
-
-
- 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));
- };
-
-
-
- FunctionCall.prototype.doCallback_ = function() {
- this.callback_.apply(null, this.lastResult_);
- };
-
-
-
- FunctionCall.prototype.handleFunctionCallback_ = function() {
- if (this.isAborted()) {
- return;
- }
-
- var args = Array.prototype.slice.call(arguments);
- this.lastResult_ = args;
- events.EventEmitter.prototype.emit.apply(this, ['callback'].concat(args));
-
- var err = args[0];
- if (err && this.retryPredicate_(err)) {
- this.backoff_.backoff(err);
- } else {
- this.state_ = FunctionCall.State_.COMPLETED;
- this.doCallback_();
- }
- };
-
-
- FunctionCall.prototype.handleBackoff_ = function(number, delay, err) {
- this.emit('backoff', number, delay, err);
- };
-
- module.exports = FunctionCall;
|