UNPKG

@randomorg/core

Version:

The official library to access the RANDOM.ORG JSON-RPC API

292 lines (262 loc) 11.1 kB
'use strict'; const { RandomOrgInsufficientBitsError, RandomOrgCacheEmptyError } = require('./RandomOrgErrors.js'); /** * Precache class for frequently used requests. */ module.exports = class RandomOrgCache { // function used to send a request #requestFunction = null; // request to be sent #request = null; // n for bulk requests #bulkRequestNumber = 0; // n for a single request #requestNumber = 0; // size of a single request in bits #requestSize = -1; // stores cached arrays of values #stack = []; // number of arrays to try to maintain in #stack #cacheSize = 10; // status of the cache #paused = false; // bits used by this cache #bitsUsed = 0; // requests used by this cache #requestsUsed = 0; // ensures #populate() does not issue parallel requests #currentlyPopulating = false; // an error which will be thrown on the next call to get() or getOrWait() #error = null; /** * Initialize class and start stack population * * ** WARNING** Should only be called by RandomOrgClient's createCache() * methods. * @param {function(Object) : Object} requestFunction Function used to send * supplied request to server. * @param {Object} request Request to send to server via requestFunction. * @param {number} cacheSize Number of request responses to try maintain. * @param {number} bulkRequestNumber If request is set to be issued in bulk, * number of result sets in a bulk request, else 0. * @param {number} requestNumber If request is set to be issued in bulk, * number of results in a single request, else 0. * @param {number} singleRequestSize Size of a single request in bits for * adjusting bulk requests if bits are in short supply on the server. */ constructor(requestFunction, request, cacheSize, bulkRequestNumber, requestNumber, singleRequestSize) { this.#requestFunction = requestFunction; this.#request = request; this.#cacheSize = cacheSize; this.#bulkRequestNumber = bulkRequestNumber; this.#requestNumber = requestNumber; this.#requestSize = singleRequestSize; this.#populate(); } /** * Function to continue issuing requests until the stack is full. * * Keep issuing requests to server until stack is full. When stack is full * if requests are being issued in bulk, wait until stack has enough space * to accommodate all of a bulk request before issuing a new request, otherwise * issue a new request every time an item in the stack has been consumed. Note * that requests are blocking ('await' is used when calling the requestFunction), * i.e., only one request will be issued by the cache at any given time. */ #populate = async () => { if (!this.#currentlyPopulating && !this.#paused) { this.#currentlyPopulating = true; let response = null; while (true) { if (this.#error != null) { break; } if (this.#bulkRequestNumber > 0) { // Is there space for a bulk response in the stack? if (this.#stack.length <= (this.#cacheSize - this.#bulkRequestNumber)) { try { response = await this.#requestFunction(this.#request); this.#addResponse(response, true); } catch (e) { // not enough bits remaining for a bulk request if (e instanceof RandomOrgInsufficientBitsError) { let bitsLeft = e.getBitsLeft(); if (bitsLeft > this.#requestSize) { // if possible, adjust request for the largest possible size let adjustedBulk = Math.floor(bitsLeft/this.#requestSize); this.#request.params.n = adjustedBulk * this.#requestNumber; response = await this.#requestFunction(this.#request); this.#addResponse(response, true); // reset to original bulk request size this.#request.params.n = this.#bulkRequestNumber * this.#requestNumber; } else { // request size cannot be adjusted this.#error = e; } } else { // Any other error thrown during in the request function this.#error = e; } } } else { // no space for a bulk request break; } } else if (this.#stack.length < this.#cacheSize) { // individual requests try { response = await this.#requestFunction(this.#request); this.#addResponse(response, false); } catch(e) { this.#error = e; } } else { // the stack is full break; } } this.#currentlyPopulating = false; } } /** * The cache will no longer continue to populate itself. */ stop() { this.#paused = true; } /** * The cache will resume populating itself if stopped. */ resume() { this.#paused = false; // check if it needs to be repopulated this.#refresh(); } /** * Checks if the cache is currently not re-populating itself. * * Values currently cached may still be retrieved with get() but no new * values are being fetched from the server. This state can be changed with * stop() and resume(). * @returns {boolean} True if cache is currently not re-populating itself, * false otherwise. */ isPaused() { return this.#paused; } /** * Gets the next response. * Note that if the cache is empty, if was constructed with unsuitable parameter * values or if the daily allowance of bits/requests has been reached, the appropriate * error will be thrown. * @returns {any[]} The next appropriate response for the request this RandomOrgCache * represents or, if stack is empty throws an error. * @throws RandomOrgCacheEmptyError if the cache is empty. */ get() { if (this.#error != null) { throw this.#error; } if (this.#stack && this.#stack.length == 0) { if (this.#paused) { throw new RandomOrgCacheEmptyError('The RandomOrgCache stack ' + 'is empty and the cache is paused. Please call resume() to ' + 'restart populating the cache.', true); } else { throw new RandomOrgCacheEmptyError('The RandomOrgCache stack ' + 'is empty, please wait for it to repopulate itself.'); } } else { let data = this.#stack.pop(); // check if it needs to be repopulated this.#refresh(); return data; } } /** * Get next response or wait until the next value is available. This method * will block until a value is available. Note: this method will throw an error * if the cache is empty and has been paused, i.e. is not being populated. If * the cache was constructed with unsuitable parameter values or the daily allowance * of bits/requests has been reached, the appropriate error will also be thrown. * @returns {Promise<any[]>} The next appropriate response for the request this * RandomOrgCache represents. * @throws RandomOrgCacheEmptyError if the cache is empty and is paused. */ async getOrWait() { try { let values = this.get(); return values; } catch (e) { if (e instanceof RandomOrgCacheEmptyError) { if (this.#paused) { // The cache is paused and will not return any values throw e; } let cachedValues = await this.#populate(); if (cachedValues == 0) { // The cache has not yet repopulated. await new Promise(r => setTimeout(r, 50)); } return this.getOrWait(); } } } /** * Gets the number of result sets remaining in the cache. * * This essentially returns how often get() may be called without * a cache refill. * @returns {number} Current number of cached results. */ getCachedValues() { return this.#stack.length; } /** * Gets the number of bits used by this cache. * @returns {number} Number of bits used. */ getBitsUsed() { return this.#bitsUsed; } /** * Gets number of requests used by this cache. * @returns {number} Number of requests used. */ getRequestsUsed() { return this.#requestsUsed; } /** * Helper function to check if the cache needs to be repopulated. */ #refresh = () => { if (this.#bulkRequestNumber > 0 && this.#stack.length <= (this.#cacheSize - this.#bulkRequestNumber)) { // bulk requests this.#populate(); } else if (this.#bulkRequestNumber <= 0 && this.#stack.length < this.#cacheSize) { // individual requests this.#populate(); } } /** * Helper function to add a response to the stack. * @param {any[]} response The response received from the server. * @param {boolean} bulk True if the cache issues bulk requests, false otherwise. */ #addResponse = (response, bulk) => { this.#requestsUsed++; this.#bitsUsed += response.result.bitsUsed; if (bulk) { let data = response.result.random.data; for (let i = 0; i < data.length; i += this.#requestNumber) { this.#stack.push(data.slice(i, i + this.#requestNumber)); } } else { this.#stack.push(response.result.random.data); } } }