UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

183 lines (182 loc) 7.27 kB
"use strict"; // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.DelayedDataLoader = exports.BackgroundDataLoader = void 0; /** * A DataLoader is an object that can be used to batch requests for fetching objects from the server for performance * reasons. */ class DataLoader { fetchBatch; maxBatchSize; comparator; pendingIdentifiers = new Set(); constructor(args) { this.fetchBatch = args.fetchBatch; this.maxBatchSize = args.maxBatchSize; this.comparator = args.comparator; } queue(identifiersToLoad) { for (const identifier of identifiersToLoad) { if (!identifier) { continue; } // If a custom comparator is provided, manually check for duplicates if (this.comparator) { let exists = false; for (const existing of this.pendingIdentifiers) { if (this.comparator(existing, identifier)) { exists = true; break; } } if (!exists) { this.pendingIdentifiers.add(identifier); } } else { // Without a comparator, Set automatically handles uniqueness this.pendingIdentifiers.add(identifier); } } } /** * prepareBatch removes an array of identifiers for data to be loaded from pendingIdentifiers and returns it. If * pendingIdentifiers contains more than maxBatchSize identifiers, then only that many are returned, but if it * contains fewer than that, all of the identifiers are returned and pendingIdentifiers is cleared. */ prepareBatch() { let nextBatch; // Since we can only fetch a defined number of identifiers at a time, we need to batch the requests if (this.pendingIdentifiers.size >= this.maxBatchSize) { nextBatch = []; // We use temp buffer here to store up until max buffer size // and clear out processed identifiers for (const identifier of this.pendingIdentifiers) { nextBatch.push(identifier); this.pendingIdentifiers.delete(identifier); if (nextBatch.length >= this.maxBatchSize) { break; } } } else { // If we have less than max buffer size, we can directly fetch the data nextBatch = Array.from(this.pendingIdentifiers); this.pendingIdentifiers.clear(); } return { identifiers: nextBatch, moreToLoad: this.pendingIdentifiers.size > 0, }; } /** * isBusy is a method for testing which returns true if the DataLoader is waiting to request or receive any data. */ isBusy() { return this.pendingIdentifiers.size > 0; } } /** * A BackgroundDataLoader is an object that can be used to batch requests for fetching objects from the server. Instead * of requesting data immediately, it will periodically check if any objects need to be requested from the server. * * It's intended to be used for loading low priority data such as information needed in response to WebSocket messages * that the user won't see immediately. */ class BackgroundDataLoader extends DataLoader { intervalId = -1; startIntervalIfNeeded(ms) { if (this.intervalId !== -1) { return; } this.intervalId = window.setInterval(() => this.fetchBatchNow(), ms); } stopInterval() { clearInterval(this.intervalId); this.intervalId = -1; } fetchBatchNow() { const { identifiers } = this.prepareBatch(); if (identifiers.length === 0) { return; } this.fetchBatch(identifiers); } isBusy() { return super.isBusy() || this.intervalId !== -1; } } exports.BackgroundDataLoader = BackgroundDataLoader; /** * A DelayedDataLoader is an object that can be used to batch requests for fetching objects from the server. Instead of * requesting data immediately, it will wait for an amount of time and then send a request to the server for all of * the data which would've been requested during that time. * * More specifically, when queue is first called, a timer will be started. Until that timer expires, any other * calls to queue will have the provided identifiers added to the ones from the initial call. When the timer * finally expires, the request will be sent to the server to fetch that data. After that, the timer will be reset and * the next call to queue will start a new one. * * DelayedDataLoader is intended to be used for loading data for components which are unaware of each other and may appear * in different places in the UI from each other which could otherwise send repeated requests for the same or similar * data as one another. */ class DelayedDataLoader extends DataLoader { wait = -1; timeoutId = -1; timeoutCallbacks = new Set(); constructor(args) { super(args); this.wait = args.wait; } queue(identifiersToLoad) { super.queue(identifiersToLoad); this.startTimeoutIfNeeded(); } queueAndWait(identifiersToLoad) { return new Promise((resolve) => { super.queue(identifiersToLoad); // Save the callback that will resolve this promise so that the caller of this method can wait for its // data to be loaded this.timeoutCallbacks.add({ identifiers: new Set(identifiersToLoad), resolve, }); this.startTimeoutIfNeeded(); }); } startTimeoutIfNeeded() { if (this.timeoutId !== -1) { return; } this.timeoutId = window.setTimeout(() => { // Ensure that timeoutId is cleared and we get a pop identifiers off of pendingIdentifiers before doing // anything async so that any calls to queue that are made while fetching this batch will be // added to the next batch instead this.timeoutId = -1; const { identifiers, moreToLoad } = this.prepareBatch(); // Start another timeout if there's still more data to load if (moreToLoad) { this.startTimeoutIfNeeded(); } this.fetchBatch(identifiers).then(() => this.resolveCompletedCallbacks(identifiers)); }, this.wait); } resolveCompletedCallbacks(identifiers) { for (const callback of this.timeoutCallbacks) { for (const identifier of identifiers) { callback.identifiers.delete(identifier); } if (callback.identifiers.size === 0) { this.timeoutCallbacks.delete(callback); callback.resolve(); } } } isBusy() { return super.isBusy() || this.timeoutCallbacks.size > 0 || this.timeoutId !== -1; } } exports.DelayedDataLoader = DelayedDataLoader;