dynatrace-api-balancer
Version:
A wrapper around Axios that balances and throttles requests across tenants, clusters and cluster nodes.
169 lines (138 loc) • 5.85 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: lib/Throttle.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: lib/Throttle.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict';
/**
* The Throttle rate-limits access to a resource over a moving time window
* using a 'leaky bucket' algorithm.
*/
class Throttle {
#queue = [];
#size = null;
#rate = null;
#fill = null;
#last = null;
/**
* Resolves next promise in the queue and keeps emptying it.
* @private
*/
#next = () => {
if (this.#queue.length === 0) return;
setInterval(this.#next, this.waitTime); // Keep emptying the queue.
this.#fill--; // Consume a drop from the bucket.
this.#queue.shift()(); // Call next 'resolve()' in the queue.
}
/**
* Creates a throttle.
* @constructor
* @param {number} limit - Number of requests allowed.
* @param {number} window - Per this time window (in ms).
*/
constructor(limit, window) {
// Window is a timespan (ms) to which the limit appplies.
this.#size = limit; // The bucket size is the maximum requests per timespan.
this.#rate = window / limit; // Drip rate: if limit = 30 req/min, add 1 drop every 2s.
this.#fill = 0; // Number of drops in the bucket.
this.#last = 0; // Time we last added more drops.
}
/** Resets the throttle to maximum capacity. */
reset() {
this.#last = (new Date()).getTime();
this.#fill = this.#size;
}
/**
* Returns how much capacity is left for this time window. This value is useful
* for selecting the least constricted resource among a pool of throttled resources.
*/
get remainder() {
return this.#fill;
}
/**
* Returns the time (ms) until the throttle opens again (plus 1ms).
* Note that this getter just returns the delay - it does not update the throttle.
*/
get nextSlot() {
const now = (new Date()).getTime();
return (this.#last + this.#rate + 1) - now;
}
/**
* Returns the time (ms) until a next request can be honored (plus 1ms if there's a wait).
* This is useful in case multiple throttles need to be checked before a request can be
* consumed. Note that this getter updates the throttle's state before it produces a value.
*/
get waitTime() {
// First refill the bucket proportional to the time elapsed since the last refill.
const now = (new Date()).getTime();
const added = Math.floor((now - this.#last) / this.#rate); // How many drops should be added?
this.#fill = Math.min(this.#size, this.#fill + added); // Don't exceed bucket capacity.
this.#last = Math.min(now, this.#last + (added * this.#rate)); // Update the refill timestamp.
// The bucket has been updated. Return 0 if there are drops, or the time until the next drop drips.
return this.#fill > 0 ? 0 : (this.#last + this.#rate + 1) - now;
}
/**
* Consumes one unit of capacity. Should only be called if {@link Throttle#waitTime waitTime} > 0.
* @example
* function doSomething() {
* const delay = myThrottle.waitTime;
* if (delay > 0)
* return "I can't do this right now, but in " + delay + "ms I can.";
*
* myThrottle.consume();
* // Do it.
* return "I did it";
* }
*/
consume() {
this.#fill--;
}
/**
* Returns a promise that is guaranteed to resolve (in FIFO order), but not sooner
* than the throttle allows. For certain use cases this provides a more convenient
* alternative compared to using the {@link Throttle#waitTime waitTime} and
* {@link Throttle#consume consume()} pair.
* @example
* async function doSomething() {
* await myThrottle.permit(); // Resolves immediately or as soon as possible.
* // Do it.
* return "I did it";
* }
*/
permit() {
return new Promise((resolve, reject) => {
const delay = this.waitTime;
if (delay <= 0) resolve();
this.#queue.push(resolve);
setInterval(this.#next, delay);
});
}
}
module.exports = Throttle;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BalancedAPIRequest.html">BalancedAPIRequest</a></li><li><a href="CancellableEventEmitter.html">CancellableEventEmitter</a></li><li><a href="CancellablePromise.html">CancellablePromise</a></li><li><a href="DirectAPIRequest.html">DirectAPIRequest</a></li><li><a href="Throttle.html">Throttle</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.7</a> on Sun Dec 19 2021 10:28:39 GMT-0600 (Central Standard Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>