mirror of
https://github.com/titanscouting/tra-analysis.git
synced 2025-02-11 13:05:46 +00:00
349 lines
13 KiB
JavaScript
349 lines
13 KiB
JavaScript
/*
|
|
* Copyright 2016, Google Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/**
|
|
* Provides function wrappers that implement page streaming and retrying.
|
|
*/
|
|
'use strict';
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
}
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var Canceller = /** @class */ (function () {
|
|
/**
|
|
* Canceller manages callback, API calls, and cancellation
|
|
* of the API calls.
|
|
* @param {APICallback=} callback
|
|
* The callback to be called asynchronously when the API call
|
|
* finishes.
|
|
* @constructor
|
|
* @property {APICallback} callback
|
|
* The callback function to be called.
|
|
* @private
|
|
*/
|
|
function Canceller(callback) {
|
|
this.callback = callback;
|
|
this.completed = false;
|
|
}
|
|
/**
|
|
* Cancels the ongoing promise.
|
|
*/
|
|
Canceller.prototype.cancel = function () {
|
|
if (this.completed) {
|
|
return;
|
|
}
|
|
this.completed = true;
|
|
if (this.cancelFunc) {
|
|
this.cancelFunc();
|
|
}
|
|
else {
|
|
this.callback(new Error('cancelled'));
|
|
}
|
|
};
|
|
/**
|
|
* Call calls the specified function. Result will be used to fulfill
|
|
* the promise.
|
|
*
|
|
* @param {function(Object, APICallback=)} aFunc
|
|
* A function for an API call.
|
|
* @param {Object} argument
|
|
* A request object.
|
|
*/
|
|
Canceller.prototype.call = function (aFunc, argument) {
|
|
var _this = this;
|
|
if (this.completed) {
|
|
return;
|
|
}
|
|
// tslint:disable-next-line no-any
|
|
var canceller = aFunc(argument, function () {
|
|
var args = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
args[_i] = arguments[_i];
|
|
}
|
|
_this.completed = true;
|
|
args.unshift(_this.callback);
|
|
setImmediate.apply(null, args);
|
|
});
|
|
this.cancelFunc = function () {
|
|
canceller.cancel();
|
|
};
|
|
};
|
|
return Canceller;
|
|
}());
|
|
exports.Canceller = Canceller;
|
|
// tslint:disable-next-line no-any
|
|
var PromiseCanceller = /** @class */ (function (_super) {
|
|
__extends(PromiseCanceller, _super);
|
|
/**
|
|
* PromiseCanceller is Canceller, but it holds a promise when
|
|
* the API call finishes.
|
|
* @param {Function} PromiseCtor - A constructor for a promise that implements
|
|
* the ES6 specification of promise.
|
|
* @constructor
|
|
* @private
|
|
*/
|
|
// tslint:disable-next-line variable-name
|
|
function PromiseCanceller(PromiseCtor) {
|
|
var _this = _super.call(this) || this;
|
|
_this.promise = new PromiseCtor(function (resolve, reject) {
|
|
_this.callback = function (err, response, next, rawResponse) {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
else {
|
|
resolve([response, next, rawResponse]);
|
|
}
|
|
};
|
|
});
|
|
_this.promise.cancel = function () {
|
|
_this.cancel();
|
|
};
|
|
return _this;
|
|
}
|
|
return PromiseCanceller;
|
|
}(Canceller));
|
|
exports.PromiseCanceller = PromiseCanceller;
|
|
/**
|
|
* Updates aFunc so that it gets called with the timeout as its final arg.
|
|
*
|
|
* This converts a function, aFunc, into another function with updated deadline.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {APIFunc} aFunc - a function to be updated.
|
|
* @param {number} timeout - to be added to the original function as it final
|
|
* positional arg.
|
|
* @param {Object} otherArgs - the additional arguments to be passed to aFunc.
|
|
* @param {Object=} abTests - the A/B testing key/value pairs.
|
|
* @return {function(Object, APICallback)}
|
|
* the function with other arguments and the timeout.
|
|
*/
|
|
function addTimeoutArg(aFunc, timeout, otherArgs, abTests) {
|
|
// TODO: this assumes the other arguments consist of metadata and options,
|
|
// which is specific to gRPC calls. Remove the hidden dependency on gRPC.
|
|
return function timeoutFunc(argument, callback) {
|
|
var now = new Date();
|
|
var options = otherArgs.options || {};
|
|
options.deadline = new Date(now.getTime() + timeout);
|
|
var metadata = otherArgs.metadataBuilder ?
|
|
otherArgs.metadataBuilder(abTests, otherArgs.headers || {}) :
|
|
null;
|
|
return aFunc(argument, metadata, options, callback);
|
|
};
|
|
}
|
|
/**
|
|
* Creates a function equivalent to aFunc, but that retries on certain
|
|
* exceptions.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {APIFunc} aFunc - A function.
|
|
* @param {RetryOptions} retry - Configures the exceptions upon which the
|
|
* function eshould retry, and the parameters to the exponential backoff retry
|
|
* algorithm.
|
|
* @param {Object} otherArgs - the additional arguments to be passed to aFunc.
|
|
* @return {function(Object, APICallback)} A function that will retry.
|
|
*/
|
|
function retryable(aFunc, retry, otherArgs) {
|
|
var delayMult = retry.backoffSettings.retryDelayMultiplier;
|
|
var maxDelay = retry.backoffSettings.maxRetryDelayMillis;
|
|
var timeoutMult = retry.backoffSettings.rpcTimeoutMultiplier;
|
|
var maxTimeout = retry.backoffSettings.maxRpcTimeoutMillis;
|
|
var delay = retry.backoffSettings.initialRetryDelayMillis;
|
|
var timeout = retry.backoffSettings.initialRpcTimeoutMillis;
|
|
/**
|
|
* Equivalent to ``aFunc``, but retries upon transient failure.
|
|
*
|
|
* Retrying is done through an exponential backoff algorithm configured
|
|
* by the options in ``retry``.
|
|
* @param {Object} argument The request object.
|
|
* @param {APICallback} callback The callback.
|
|
* @return {function()} cancel function.
|
|
*/
|
|
return function retryingFunc(argument, callback) {
|
|
var canceller;
|
|
var timeoutId;
|
|
var now = new Date();
|
|
var deadline;
|
|
if (retry.backoffSettings.totalTimeoutMillis) {
|
|
deadline = now.getTime() + retry.backoffSettings.totalTimeoutMillis;
|
|
}
|
|
var retries = 0;
|
|
var maxRetries = retry.backoffSettings.maxRetries;
|
|
// TODO: define A/B testing values for retry behaviors.
|
|
/** Repeat the API call as long as necessary. */
|
|
function repeat() {
|
|
timeoutId = null;
|
|
if (deadline && now.getTime() >= deadline) {
|
|
callback(new Error('Retry total timeout exceeded before any ' +
|
|
'response was received'));
|
|
return;
|
|
}
|
|
if (retries && retries >= maxRetries) {
|
|
callback(new Error('Exceeded maximum number of retries before any ' +
|
|
'response was received'));
|
|
return;
|
|
}
|
|
retries++;
|
|
var toCall = addTimeoutArg(aFunc, timeout, otherArgs);
|
|
canceller = toCall(argument, function (err, response, next, rawResponse) {
|
|
if (!err) {
|
|
callback(null, response, next, rawResponse);
|
|
return;
|
|
}
|
|
canceller = null;
|
|
if (retry.retryCodes.indexOf(err.code) < 0) {
|
|
err.note = 'Exception occurred in retry method that was ' +
|
|
'not classified as transient';
|
|
callback(err);
|
|
}
|
|
else {
|
|
var toSleep = Math.random() * delay;
|
|
timeoutId = setTimeout(function () {
|
|
now = new Date();
|
|
delay = Math.min(delay * delayMult, maxDelay);
|
|
timeout = Math.min(timeout * timeoutMult, maxTimeout, deadline - now.getTime());
|
|
repeat();
|
|
}, toSleep);
|
|
}
|
|
});
|
|
}
|
|
if (maxRetries && deadline) {
|
|
callback(new Error('Cannot set both totalTimeoutMillis and maxRetries ' +
|
|
'in backoffSettings.'));
|
|
}
|
|
else {
|
|
repeat();
|
|
}
|
|
return {
|
|
cancel: function () {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
if (canceller) {
|
|
canceller.cancel();
|
|
}
|
|
else {
|
|
callback(new Error('cancelled'));
|
|
}
|
|
},
|
|
};
|
|
};
|
|
}
|
|
/**
|
|
* Creates an API caller for normal methods.
|
|
*
|
|
* @private
|
|
* @constructor
|
|
*/
|
|
var NormalApiCaller = /** @class */ (function () {
|
|
function NormalApiCaller() {
|
|
}
|
|
NormalApiCaller.prototype.init = function (settings, callback) {
|
|
if (callback) {
|
|
return new Canceller(callback);
|
|
}
|
|
return new PromiseCanceller(settings.promise);
|
|
};
|
|
NormalApiCaller.prototype.wrap = function (func) {
|
|
return func;
|
|
};
|
|
NormalApiCaller.prototype.call = function (apiCall, argument, settings, canceller) {
|
|
canceller.call(apiCall, argument);
|
|
};
|
|
NormalApiCaller.prototype.fail = function (canceller, err) {
|
|
canceller.callback(err);
|
|
};
|
|
NormalApiCaller.prototype.result = function (canceller) {
|
|
if (canceller.promise) {
|
|
return canceller.promise;
|
|
}
|
|
return;
|
|
};
|
|
return NormalApiCaller;
|
|
}());
|
|
exports.NormalApiCaller = NormalApiCaller;
|
|
/**
|
|
* Converts an rpc call into an API call governed by the settings.
|
|
*
|
|
* In typical usage, `func` will be a promsie to a callable used to make an rpc
|
|
* request. This will mostly likely be a bound method from a request stub used
|
|
* to make an rpc call. It is not a direct function but a Promise instance,
|
|
* because of its asynchronism (typically, obtaining the auth information).
|
|
*
|
|
* The result is a function which manages the API call with the given settings
|
|
* and the options on the invocation.
|
|
*
|
|
* @param {Promise.<APIFunc>} funcWithAuth - is a promise to be used to make
|
|
* a bare rpc call. This is a Promise instead of a bare function because
|
|
* the rpc call will be involeved with asynchronous authentications.
|
|
* @param {CallSettings} settings - provides the settings for this call
|
|
* @param {Object=} optDescriptor - optionally specify the descriptor for
|
|
* the method call.
|
|
* @return {APICall} func - a bound method on a request stub used
|
|
* to make an rpc call.
|
|
*/
|
|
function createApiCall(funcWithAuth, settings,
|
|
// tslint:disable-next-line no-any
|
|
optDescriptor) {
|
|
var apiCaller = optDescriptor ? optDescriptor.apiCaller(settings) : new NormalApiCaller();
|
|
return function apiCallInner(request, callOptions, callback) {
|
|
var thisSettings = settings.merge(callOptions);
|
|
var status = apiCaller.init(thisSettings, callback);
|
|
funcWithAuth
|
|
.then(function (func) {
|
|
func = apiCaller.wrap(func);
|
|
var retry = thisSettings.retry;
|
|
if (retry && retry.retryCodes && retry.retryCodes.length > 0) {
|
|
return retryable(func, thisSettings.retry, thisSettings.otherArgs);
|
|
}
|
|
return addTimeoutArg(func, thisSettings.timeout, thisSettings.otherArgs);
|
|
})
|
|
.then(function (apiCall) {
|
|
apiCaller.call(apiCall, request, thisSettings, status);
|
|
})
|
|
.catch(function (err) {
|
|
apiCaller.fail(status, err);
|
|
});
|
|
return apiCaller.result(status);
|
|
};
|
|
}
|
|
exports.createApiCall = createApiCall;
|
|
//# sourceMappingURL=api_callable.js.map
|