ran-boilerplate
Version:
React . Apollo (GraphQL) . Next.js Toolkit
236 lines (213 loc) • 6.68 kB
JavaScript
/*!
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/*!
* Injected.
*
* @see Firestore
*/
let Firestore;
/*
* @module firestore/backoff
* @private
*
* Contains backoff logic to facilitate RPC error handling. This class derives
* its implementation from the Firestore Mobile Web Client.
*
* @see https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/remote/backoff.ts
*/
/*!
* The default initial backoff time in milliseconds after an error.
* Set to 1s according to https://cloud.google.com/apis/design/errors.
*/
const DEFAULT_BACKOFF_INITIAL_DELAY_MS = 1000;
/*!
* The default maximum backoff time in milliseconds.
*/
const DEFAULT_BACKOFF_MAX_DELAY_MS = 60 * 1000;
/*!
* The default factor to increase the backup by after each failed attempt.
*/
const DEFAULT_BACKOFF_FACTOR = 1.5;
/*!
* The default jitter to distribute the backoff attempts by (0 means no
* randomization, 1.0 means +/-50% randomization).
*/
const DEFAULT_JITTER_FACTOR = 1.0;
/*!
* The timeout handler used by `ExponentialBackoff`.
*/
let delayExecution = setTimeout;
/**
* Allows overriding of the timeout handler used by the exponential backoff
* implementation. If not invoked, we default to `setTimeout()`.
*
* Used only in testing.
*
* @private
* @param {function} handler A handler than matches the API of `setTimeout()`.
*/
function setTimeoutHandler(handler) {
delayExecution = handler;
}
/**
* A helper for running delayed tasks following an exponential backoff curve
* between attempts.
*
* Each delay is made up of a "base" delay which follows the exponential
* backoff curve, and a "jitter" (+/- 50% by default) that is calculated and
* added to the base delay. This prevents clients from accidentally
* synchronizing their delays causing spikes of load to the backend.
*
* @private
*/
class ExponentialBackoff {
/**
* @param {number=} options.initialDelayMs Optional override for the initial
* retry delay.
* @param {number=} options.backoffFactor Optional override for the
* exponential backoff factor.
* @param {number=} options.maxDelayMs Optional override for the maximum
* retry delay.
* @param {number=} options.jitterFactor Optional override to control the
* jitter factor by which to randomize attempts (0 means no randomization,
* 1.0 means +/-50% randomization). It is suggested not to exceed this range.
*/
constructor(options) {
options = options || {};
/**
* The initial delay (used as the base delay on the first retry attempt).
* Note that jitter will still be applied, so the actual delay could be as
* little as 0.5*initialDelayMs (based on a jitter factor of 1.0).
*
* @type {number}
* @private
*/
this._initialDelayMs =
options.initialDelayMs !== undefined
? options.initialDelayMs
: DEFAULT_BACKOFF_INITIAL_DELAY_MS;
/**
* The multiplier to use to determine the extended base delay after each
* attempt.
* @type {number}
* @private
*/
this._backoffFactor =
options.backoffFactor !== undefined
? options.backoffFactor
: DEFAULT_BACKOFF_FACTOR;
/**
* The maximum base delay after which no further backoff is performed.
* Note that jitter will still be applied, so the actual delay could be as
* much as 1.5*maxDelayMs (based on a jitter factor of 1.0).
*
* @type {number}
* @private
*/
this._maxDelayMs =
options.maxDelayMs !== undefined
? options.maxDelayMs
: DEFAULT_BACKOFF_MAX_DELAY_MS;
/**
* The jitter factor that controls the random distribution of the backoff
* points.
*
* @type {number}
* @private
*/
this._jitterFactor =
options.jitterFactor !== undefined
? options.jitterFactor
: DEFAULT_JITTER_FACTOR;
/**
* The backoff delay of the current attempt.
* @type {number}
* @private
*/
this._currentBaseMs = 0;
}
/**
* Resets the backoff delay.
*
* The very next backoffAndWait() will have no delay. If it is called again
* (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
* subsequent ones will increase according to the backoffFactor.
*
* @private
*/
reset() {
this._currentBaseMs = 0;
}
/**
* Resets the backoff delay to the maximum delay (e.g. for use after a
* RESOURCE_EXHAUSTED error).
*
* @private
*/
resetToMax() {
this._currentBaseMs = this._maxDelayMs;
}
/**
* Returns a promise that resolves after currentDelayMs, and increases the
* delay for any subsequent attempts.
*
* @private
* @return {Promise.<void>} A Promise that resolves when the current delay
* elapsed.
*/
backoffAndWait() {
// First schedule using the current base (which may be 0 and should be
// honored as such).
const delayWithJitterMs = this._currentBaseMs + this._jitterDelayMs();
if (this._currentBaseMs > 0) {
Firestore.log(
'ExponentialBackoff.backoffAndWait',
`Backing off for ${delayWithJitterMs} ms ` +
`(base delay: ${this._currentBaseMs} ms)`
);
}
// Apply backoff factor to determine next delay and ensure it is within
// bounds.
this._currentBaseMs *= this._backoffFactor;
if (this._currentBaseMs < this._initialDelayMs) {
this._currentBaseMs = this._initialDelayMs;
}
if (this._currentBaseMs > this._maxDelayMs) {
this._currentBaseMs = this._maxDelayMs;
}
return new Promise(resolve => {
delayExecution(resolve, delayWithJitterMs);
});
}
/**
* Returns a randomized "jitter" delay based on the current base and jitter
* factor.
*
* @private
* @returns {number} The jitter to apply based on the current delay.
*/
_jitterDelayMs() {
return (Math.random() - 0.5) * this._jitterFactor * this._currentBaseMs;
}
}
module.exports = FirestoreType => {
Firestore = FirestoreType;
return {
ExponentialBackoff,
setTimeoutHandler,
};
};