349 lines
13 KiB
JavaScript
Raw Normal View History

2019-01-06 13:14:45 -06:00
/*
* 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