import { SentryError } from './error.js'; import { rejectedSyncPromise, SyncPromise, resolvedSyncPromise } from './syncpromise.js'; /** * Creates an new PromiseBuffer object with the specified limit * @param limit max number of promises that can be stored in the buffer */ function makePromiseBuffer(limit) { const buffer = []; function isReady() { return limit === undefined || buffer.length < limit; } /** * Remove a promise from the queue. * * @param task Can be any PromiseLike * @returns Removed promise. */ function remove(task) { return buffer.splice(buffer.indexOf(task), 1)[0]; } /** * Add a promise (representing an in-flight action) to the queue, and set it to remove itself on fulfillment. * * @param taskProducer A function producing any PromiseLike; In previous versions this used to be `task: * PromiseLike`, but under that model, Promises were instantly created on the call-site and their executor * functions therefore ran immediately. Thus, even if the buffer was full, the action still happened. By * requiring the promise to be wrapped in a function, we can defer promise creation until after the buffer * limit check. * @returns The original promise. */ function add(taskProducer) { if (!isReady()) { return rejectedSyncPromise(new SentryError('Not adding Promise because buffer limit was reached.')); } // start the task and add its promise to the queue const task = taskProducer(); if (buffer.indexOf(task) === -1) { buffer.push(task); } void task .then(() => remove(task)) // Use `then(null, rejectionHandler)` rather than `catch(rejectionHandler)` so that we can use `PromiseLike` // rather than `Promise`. `PromiseLike` doesn't have a `.catch` method, making its polyfill smaller. (ES5 didn't // have promises, so TS has to polyfill when down-compiling.) .then(null, () => remove(task).then(null, () => { // We have to add another catch here because `remove()` starts a new promise chain. }), ); return task; } /** * Wait for all promises in the queue to resolve or for timeout to expire, whichever comes first. * * @param timeout The time, in ms, after which to resolve to `false` if the queue is still non-empty. Passing `0` (or * not passing anything) will make the promise wait as long as it takes for the queue to drain before resolving to * `true`. * @returns A promise which will resolve to `true` if the queue is already empty or drains before the timeout, and * `false` otherwise */ function drain(timeout) { return new SyncPromise((resolve, reject) => { let counter = buffer.length; if (!counter) { return resolve(true); } // wait for `timeout` ms and then resolve to `false` (if not cancelled first) const capturedSetTimeout = setTimeout(() => { if (timeout && timeout > 0) { resolve(false); } }, timeout); // if all promises resolve in time, cancel the timer and resolve to `true` buffer.forEach(item => { void resolvedSyncPromise(item).then(() => { if (!--counter) { clearTimeout(capturedSetTimeout); resolve(true); } }, reject); }); }); } return { $: buffer, add, drain, }; } export { makePromiseBuffer }; //# sourceMappingURL=promisebuffer.js.map