Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

function_call.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Copyright (c) 2012 Mathieu Turcotte
  2. // Licensed under the MIT license.
  3. var events = require('events');
  4. var precond = require('precond');
  5. var util = require('util');
  6. var Backoff = require('./backoff');
  7. var FibonacciBackoffStrategy = require('./strategy/fibonacci');
  8. // Wraps a function to be called in a backoff loop.
  9. function FunctionCall(fn, args, callback) {
  10. events.EventEmitter.call(this);
  11. precond.checkIsFunction(fn, 'Expected fn to be a function.');
  12. precond.checkIsArray(args, 'Expected args to be an array.');
  13. precond.checkIsFunction(callback, 'Expected callback to be a function.');
  14. this.function_ = fn;
  15. this.arguments_ = args;
  16. this.callback_ = callback;
  17. this.lastResult_ = [];
  18. this.numRetries_ = 0;
  19. this.backoff_ = null;
  20. this.strategy_ = null;
  21. this.failAfter_ = -1;
  22. this.retryPredicate_ = FunctionCall.DEFAULT_RETRY_PREDICATE_;
  23. this.state_ = FunctionCall.State_.PENDING;
  24. }
  25. util.inherits(FunctionCall, events.EventEmitter);
  26. // States in which the call can be.
  27. FunctionCall.State_ = {
  28. // Call isn't started yet.
  29. PENDING: 0,
  30. // Call is in progress.
  31. RUNNING: 1,
  32. // Call completed successfully which means that either the wrapped function
  33. // returned successfully or the maximal number of backoffs was reached.
  34. COMPLETED: 2,
  35. // The call was aborted.
  36. ABORTED: 3
  37. };
  38. // The default retry predicate which considers any error as retriable.
  39. FunctionCall.DEFAULT_RETRY_PREDICATE_ = function(err) {
  40. return true;
  41. };
  42. // Checks whether the call is pending.
  43. FunctionCall.prototype.isPending = function() {
  44. return this.state_ == FunctionCall.State_.PENDING;
  45. };
  46. // Checks whether the call is in progress.
  47. FunctionCall.prototype.isRunning = function() {
  48. return this.state_ == FunctionCall.State_.RUNNING;
  49. };
  50. // Checks whether the call is completed.
  51. FunctionCall.prototype.isCompleted = function() {
  52. return this.state_ == FunctionCall.State_.COMPLETED;
  53. };
  54. // Checks whether the call is aborted.
  55. FunctionCall.prototype.isAborted = function() {
  56. return this.state_ == FunctionCall.State_.ABORTED;
  57. };
  58. // Sets the backoff strategy to use. Can only be called before the call is
  59. // started otherwise an exception will be thrown.
  60. FunctionCall.prototype.setStrategy = function(strategy) {
  61. precond.checkState(this.isPending(), 'FunctionCall in progress.');
  62. this.strategy_ = strategy;
  63. return this; // Return this for chaining.
  64. };
  65. // Sets the predicate which will be used to determine whether the errors
  66. // returned from the wrapped function should be retried or not, e.g. a
  67. // network error would be retriable while a type error would stop the
  68. // function call.
  69. FunctionCall.prototype.retryIf = function(retryPredicate) {
  70. precond.checkState(this.isPending(), 'FunctionCall in progress.');
  71. this.retryPredicate_ = retryPredicate;
  72. return this;
  73. };
  74. // Returns all intermediary results returned by the wrapped function since
  75. // the initial call.
  76. FunctionCall.prototype.getLastResult = function() {
  77. return this.lastResult_.concat();
  78. };
  79. // Returns the number of times the wrapped function call was retried.
  80. FunctionCall.prototype.getNumRetries = function() {
  81. return this.numRetries_;
  82. };
  83. // Sets the backoff limit.
  84. FunctionCall.prototype.failAfter = function(maxNumberOfRetry) {
  85. precond.checkState(this.isPending(), 'FunctionCall in progress.');
  86. this.failAfter_ = maxNumberOfRetry;
  87. return this; // Return this for chaining.
  88. };
  89. // Aborts the call.
  90. FunctionCall.prototype.abort = function() {
  91. if (this.isCompleted() || this.isAborted()) {
  92. return;
  93. }
  94. if (this.isRunning()) {
  95. this.backoff_.reset();
  96. }
  97. this.state_ = FunctionCall.State_.ABORTED;
  98. this.lastResult_ = [new Error('Backoff aborted.')];
  99. this.emit('abort');
  100. this.doCallback_();
  101. };
  102. // Initiates the call to the wrapped function. Accepts an optional factory
  103. // function used to create the backoff instance; used when testing.
  104. FunctionCall.prototype.start = function(backoffFactory) {
  105. precond.checkState(!this.isAborted(), 'FunctionCall is aborted.');
  106. precond.checkState(this.isPending(), 'FunctionCall already started.');
  107. var strategy = this.strategy_ || new FibonacciBackoffStrategy();
  108. this.backoff_ = backoffFactory ?
  109. backoffFactory(strategy) :
  110. new Backoff(strategy);
  111. this.backoff_.on('ready', this.doCall_.bind(this, true /* isRetry */));
  112. this.backoff_.on('fail', this.doCallback_.bind(this));
  113. this.backoff_.on('backoff', this.handleBackoff_.bind(this));
  114. if (this.failAfter_ > 0) {
  115. this.backoff_.failAfter(this.failAfter_);
  116. }
  117. this.state_ = FunctionCall.State_.RUNNING;
  118. this.doCall_(false /* isRetry */);
  119. };
  120. // Calls the wrapped function.
  121. FunctionCall.prototype.doCall_ = function(isRetry) {
  122. if (isRetry) {
  123. this.numRetries_++;
  124. }
  125. var eventArgs = ['call'].concat(this.arguments_);
  126. events.EventEmitter.prototype.emit.apply(this, eventArgs);
  127. var callback = this.handleFunctionCallback_.bind(this);
  128. this.function_.apply(null, this.arguments_.concat(callback));
  129. };
  130. // Calls the wrapped function's callback with the last result returned by the
  131. // wrapped function.
  132. FunctionCall.prototype.doCallback_ = function() {
  133. this.callback_.apply(null, this.lastResult_);
  134. };
  135. // Handles wrapped function's completion. This method acts as a replacement
  136. // for the original callback function.
  137. FunctionCall.prototype.handleFunctionCallback_ = function() {
  138. if (this.isAborted()) {
  139. return;
  140. }
  141. var args = Array.prototype.slice.call(arguments);
  142. this.lastResult_ = args; // Save last callback arguments.
  143. events.EventEmitter.prototype.emit.apply(this, ['callback'].concat(args));
  144. var err = args[0];
  145. if (err && this.retryPredicate_(err)) {
  146. this.backoff_.backoff(err);
  147. } else {
  148. this.state_ = FunctionCall.State_.COMPLETED;
  149. this.doCallback_();
  150. }
  151. };
  152. // Handles the backoff event by reemitting it.
  153. FunctionCall.prototype.handleBackoff_ = function(number, delay, err) {
  154. this.emit('backoff', number, delay, err);
  155. };
  156. module.exports = FunctionCall;