UNPKG

4.98 kBJavaScriptView Raw
1/*
2 * Copyright (c) 2012 Mathieu Turcotte
3 * Licensed under the MIT license.
4 */
5
6var events = require('events');
7var util = require('util');
8
9var Backoff = require('./backoff');
10var FibonacciBackoffStrategy = require('./strategy/fibonacci');
11
12/**
13 * Returns true if the specified value is a function
14 * @param val Variable to test.
15 * @return Whether variable is a function.
16 */
17function isFunction(val) {
18 return typeof val == 'function';
19}
20
21/**
22 * Manages the calling of a function in a backoff loop.
23 * @param fn Function to wrap in a backoff handler.
24 * @param args Array of function's arguments.
25 * @param callback Function's callback.
26 * @constructor
27 */
28function FunctionCall(fn, args, callback) {
29 events.EventEmitter.call(this);
30
31 if (!isFunction(fn)) {
32 throw new Error('fn should be a function.' +
33 'Actual: ' + typeof fn);
34 }
35
36 if (!isFunction(callback)) {
37 throw new Error('callback should be a function.' +
38 'Actual: ' + typeof fn);
39 }
40
41 this.function_ = fn;
42 this.arguments_ = args;
43 this.callback_ = callback;
44 this.results_ = [];
45
46 this.backoff_ = null;
47 this.strategy_ = null;
48 this.failAfter_ = -1;
49
50 this.called_ = false;
51 this.aborted_ = false;
52}
53util.inherits(FunctionCall, events.EventEmitter);
54
55/**
56 * Creates a backoff instance from the provided strategy; defaults to a
57 * Fibonacci backoff strategy if none is provided.
58 * @param strategy Optional strategy to use when instantiating the backoff.
59 * @return A backoff instance.
60 * @private
61 */
62FunctionCall.backoffFactory_ = function(strategy) {
63 return new Backoff(strategy || new FibonacciBackoffStrategy());
64};
65
66/**
67 * Sets the backoff strategy.
68 * @param strategy The backoff strategy to use.
69 * @return Itself for chaining.
70 */
71FunctionCall.prototype.setStrategy = function(strategy) {
72 if (this.called_) {
73 throw new Error('Call in progress.');
74 }
75 this.strategy_ = strategy;
76 return this;
77};
78
79/**
80 * Returns all intermediary results returned by the wrapped function since
81 * the initial call.
82 * @return An array of intermediary results.
83 */
84FunctionCall.prototype.getResults = function() {
85 return this.results_.concat();
86};
87
88/**
89 * Sets the backoff limit.
90 * @param maxNumberOfRetry The maximum number of backoffs.
91 * @return Itself for chaining.
92 */
93FunctionCall.prototype.failAfter = function(maxNumberOfRetry) {
94 if (this.called_) {
95 throw new Error('Call in progress.');
96 }
97 this.failAfter_ = maxNumberOfRetry;
98 return this;
99};
100
101/**
102 * Aborts the current call.
103 */
104FunctionCall.prototype.abort = function() {
105 this.aborted_ = true;
106 if (this.called_) {
107 this.backoff_.reset();
108 }
109};
110
111/**
112 * Initiates the call to the wrapped function.
113 * @param backoffFactory Optional factory method used to create the backoff
114 * instance.
115 */
116FunctionCall.prototype.start = function(backoffFactory) {
117 if (this.aborted_) {
118 return;
119 }
120
121 if (this.called_) {
122 throw new Error('Call in progress.');
123 }
124
125 backoffFactory = backoffFactory || FunctionCall.backoffFactory_;
126
127 this.backoff_ = backoffFactory(this.strategy_);
128 this.backoff_.on('ready', this.doCall_.bind(this));
129 this.backoff_.on('fail', this.doCallback_.bind(this));
130 this.backoff_.on('backoff', this.handleBackoff_.bind(this));
131
132 if (this.failAfter_ > 0) {
133 this.backoff_.failAfter(this.failAfter_);
134 }
135
136 this.called_ = true;
137 this.doCall_();
138};
139
140/**
141 * Calls the wrapped function.
142 * @private
143 */
144FunctionCall.prototype.doCall_ = function() {
145 var eventArgs = ['call'].concat(this.arguments_);
146 events.EventEmitter.prototype.emit.apply(this, eventArgs);
147 var callback = this.handleFunctionCallback_.bind(this);
148 this.function_.apply(null, this.arguments_.concat(callback));
149};
150
151/**
152 * Calls the wrapped function's callback with the last result returned by the
153 * wrapped function.
154 * @private
155 */
156FunctionCall.prototype.doCallback_ = function() {
157 var args = this.results_[this.results_.length - 1];
158 this.callback_.apply(null, args);
159};
160
161/**
162 * Handles wrapped function's completion. This method acts as a replacement
163 * for the original callback function.
164 * @private
165 */
166FunctionCall.prototype.handleFunctionCallback_ = function() {
167 if (this.aborted_) {
168 return;
169 }
170
171 var args = Array.prototype.slice.call(arguments);
172 this.results_.push(args); // Save callback arguments.
173 events.EventEmitter.prototype.emit.apply(this, ['callback'].concat(args));
174
175 if (args[0]) {
176 this.backoff_.backoff(args[0]);
177 } else {
178 this.doCallback_();
179 }
180};
181
182/**
183 * Handles backoff event.
184 * @param number Backoff number.
185 * @param delay Backoff delay.
186 * @param err The error that caused the backoff.
187 * @private
188 */
189FunctionCall.prototype.handleBackoff_ = function(number, delay, err) {
190 this.emit('backoff', number, delay, err);
191};
192
193module.exports = FunctionCall;