yahoo-finance2
Version:
JS API for Yahoo Finance
152 lines (151 loc) • 6.2 kB
JavaScript
;
/**
* Utility function will debounce multiple calls and combine them into a
* single {@linkcode [modules/quote] quote} call, i.e. you'll call `quoteCombine()` many times, and
* 50ms (default) after the last call, `quote()` will be called once so that only a single
* HTTP request is sent to collect the data for all symbols.
*
* @example
* ```ts
* import YahooFinance from 'yahoo-finance2';
* const yahooFinance = new YahooFinance();
*
* // Only a single HTTP request will be made for all of these.
* databaseResults.forEach(async (row) => {
* const result = await yahooFinance.quoteCombine(row.symbol);
* // do something
* });
*
* // Consider asking only for what you need to save on bandwidth and latency
* const fields = [ "regularMarketPrice", "regularMarketTime" ];
* const result = await yahooFinance.quoteCombine("TSLA", { fields });
* ```
*
* ### Notes:
* - Each `quoteCombine()` call receives the result for only the symbol it asked for.
*
* - Query options (i.e. `fields`, above) and the shape of the return result are identical to that of quote().
*
* - If you call `quoteCombine()` multiple times with different {@linkcode QuoteOptions}, `quote()` will be called separately for each unique set of queryOptions and its associated set of symbols.
*
* - It's fine if your code calls `quoteCombine()` many times for the same symbol. The symbol will be queried only once, and returned many times.
*
* - You can override the defaults with `const yahooFinance = new YahooFinance({
* quoteCombine: QuoteCombineOptions
* })`, see {@linkcode QuoteCombineOptions} for more details.
*
* @module quoteCombine
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultOptions = void 0;
exports.default = quoteCombine;
const index_js_1 = require("../lib/validate/index.js");
const quote_js_1 = __importDefault(require("../modules/quote.js"));
const validateAndCoerceTypes_js_1 = __importDefault(require("../lib/validateAndCoerceTypes.js"));
const quote_schema_js_1 = __importDefault(require("../modules/quote.schema.js"));
const definitions = (0, index_js_1.getTypedDefinitions)(quote_schema_js_1.default);
const slugMap = new Map();
/** quoteCombine defaults. Override with `new Yahoo Finance({ quoteCombine: options })`. */
exports.defaultOptions = {
maxSymbolsPerRequest: 100,
debounceTime: 50,
};
function quoteCombine(query, _queryOptionsOverrides = {}, moduleOptions) {
const symbol = query;
if (typeof symbol !== "string") {
throw new Error("quoteCombine expects a string query parameter, received: " +
JSON.stringify(symbol, null, 2));
}
if (_queryOptionsOverrides.return && _queryOptionsOverrides.return !== "array") {
throw new Error("Can't specify other return types using quoteCombine()");
}
const queryOptionsOverrides = _queryOptionsOverrides;
const validateOptions = !moduleOptions ||
moduleOptions.validateOptions === undefined ||
moduleOptions.validateOptions === true;
try {
(0, validateAndCoerceTypes_js_1.default)({
source: "quoteCombine",
type: "options",
object: queryOptionsOverrides,
definitions,
schemaOrSchemaKey: "#/definitions/QuoteOptions",
options: this._opts.validation,
logger: this._opts.logger,
logObj: this._logObj,
versionCheck: this._opts.versionCheck,
});
}
catch (error) {
if (validateOptions)
throw error;
}
// Make sure we only combine requests with same options
const _slug = JSON.stringify(queryOptionsOverrides);
let entry = slugMap.get(_slug);
if (!entry) {
entry = {
timeout: null,
queryOptionsOverrides,
symbols: new Map(),
};
slugMap.set(_slug, entry);
}
let i = 1, slug = _slug;
while (entry && entry.symbols.size >= this._opts.quoteCombine.maxSymbolsPerRequest) {
slug = `${_slug}-${i++}`;
entry = slugMap.get(slug);
}
if (!entry) {
entry = {
timeout: null,
queryOptionsOverrides,
symbols: new Map(),
};
slugMap.set(slug, entry);
}
if (entry.timeout)
clearTimeout(entry.timeout);
return new Promise((resolve, reject) => {
let symbolPromiseCallbacks = entry.symbols.get(symbol);
/* istanbul ignore else */
if (!symbolPromiseCallbacks) {
symbolPromiseCallbacks = [];
entry.symbols.set(symbol, symbolPromiseCallbacks);
}
symbolPromiseCallbacks.push({ resolve, reject });
entry.timeout = setTimeout(() => {
slugMap.delete(slug);
const symbols = Array.from(entry.symbols.keys());
const thisQuote = quote_js_1.default.bind(this);
thisQuote(symbols, queryOptionsOverrides, {
...moduleOptions,
validateResult: true,
}).then((results) => {
for (const result of results) {
for (const promise of entry.symbols.get(result.symbol)) {
promise.resolve(result);
promise.resolved = true;
}
}
// Check for symbols we asked for and didn't get back,
// e.g. non-existant symbols (#150)
for (const [_symbol, promises] of entry.symbols) {
for (const promise of promises) {
if (!promise.resolved) {
promise.resolve(undefined);
}
}
}
}).catch((error) => {
for (const symbolPromiseCallbacks of entry.symbols.values()) {
for (const promise of symbolPromiseCallbacks)
promise.reject(error);
}
});
}, this._opts.quoteCombine.debounceTime);
});
}