import * as Collections from '../utils/collections';
import Util from '../util';
import { OneOffTimer as Timer } from '../utils/timers';
import Strategy from './strategy';
import StrategyOptions from './strategy_options';
/** Loops through strategies with optional timeouts.
*
* Options:
* - loop - whether it should loop through the substrategy list
* - timeout - initial timeout for a single substrategy
* - timeoutLimit - maximum timeout
*
* @param {Strategy[]} strategies
* @param {Object} options
*/
export default class SequentialStrategy implements Strategy {
strategies: Strategy[];
loop: boolean;
failFast: boolean;
timeout: number;
timeoutLimit: number;
constructor(strategies: Strategy[], options: StrategyOptions) {
this.strategies = strategies;
this.loop = Boolean(options.loop);
this.failFast = Boolean(options.failFast);
this.timeout = options.timeout;
this.timeoutLimit = options.timeoutLimit;
}
isSupported(): boolean {
return Collections.any(this.strategies, Util.method('isSupported'));
}
connect(minPriority: number, callback: Function) {
var strategies = this.strategies;
var current = 0;
var timeout = this.timeout;
var runner = null;
var tryNextStrategy = (error, handshake) => {
if (handshake) {
callback(null, handshake);
} else {
current = current + 1;
if (this.loop) {
current = current % strategies.length;
}
if (current < strategies.length) {
if (timeout) {
timeout = timeout * 2;
if (this.timeoutLimit) {
timeout = Math.min(timeout, this.timeoutLimit);
}
}
runner = this.tryStrategy(
strategies[current],
minPriority,
{ timeout, failFast: this.failFast },
tryNextStrategy
);
} else {
callback(true);
}
}
};
runner = this.tryStrategy(
strategies[current],
minPriority,
{ timeout: timeout, failFast: this.failFast },
tryNextStrategy
);
return {
abort: function() {
runner.abort();
},
forceMinPriority: function(p) {
minPriority = p;
if (runner) {
runner.forceMinPriority(p);
}
}
};
}
private tryStrategy(
strategy: Strategy,
minPriority: number,
options: StrategyOptions,
callback: Function
) {
var timer = null;
var runner = null;
if (options.timeout > 0) {
timer = new Timer(options.timeout, function() {
runner.abort();
callback(true);
});
}
runner = strategy.connect(minPriority, function(error, handshake) {
if (error && timer && timer.isRunning() && !options.failFast) {
// advance to the next strategy after the timeout
return;
}
if (timer) {
timer.ensureAborted();
}
callback(error, handshake);
});
return {
abort: function() {
if (timer) {
timer.ensureAborted();
}
runner.abort();
},
forceMinPriority: function(p) {
runner.forceMinPriority(p);
}
};
}
}
|