promisebuffer.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { SentryError } from './error.js';
  2. import { rejectedSyncPromise, SyncPromise, resolvedSyncPromise } from './syncpromise.js';
  3. /**
  4. * Creates an new PromiseBuffer object with the specified limit
  5. * @param limit max number of promises that can be stored in the buffer
  6. */
  7. function makePromiseBuffer(limit) {
  8. const buffer = [];
  9. function isReady() {
  10. return limit === undefined || buffer.length < limit;
  11. }
  12. /**
  13. * Remove a promise from the queue.
  14. *
  15. * @param task Can be any PromiseLike<T>
  16. * @returns Removed promise.
  17. */
  18. function remove(task) {
  19. return buffer.splice(buffer.indexOf(task), 1)[0];
  20. }
  21. /**
  22. * Add a promise (representing an in-flight action) to the queue, and set it to remove itself on fulfillment.
  23. *
  24. * @param taskProducer A function producing any PromiseLike<T>; In previous versions this used to be `task:
  25. * PromiseLike<T>`, but under that model, Promises were instantly created on the call-site and their executor
  26. * functions therefore ran immediately. Thus, even if the buffer was full, the action still happened. By
  27. * requiring the promise to be wrapped in a function, we can defer promise creation until after the buffer
  28. * limit check.
  29. * @returns The original promise.
  30. */
  31. function add(taskProducer) {
  32. if (!isReady()) {
  33. return rejectedSyncPromise(new SentryError('Not adding Promise because buffer limit was reached.'));
  34. }
  35. // start the task and add its promise to the queue
  36. const task = taskProducer();
  37. if (buffer.indexOf(task) === -1) {
  38. buffer.push(task);
  39. }
  40. void task
  41. .then(() => remove(task))
  42. // Use `then(null, rejectionHandler)` rather than `catch(rejectionHandler)` so that we can use `PromiseLike`
  43. // rather than `Promise`. `PromiseLike` doesn't have a `.catch` method, making its polyfill smaller. (ES5 didn't
  44. // have promises, so TS has to polyfill when down-compiling.)
  45. .then(null, () =>
  46. remove(task).then(null, () => {
  47. // We have to add another catch here because `remove()` starts a new promise chain.
  48. }),
  49. );
  50. return task;
  51. }
  52. /**
  53. * Wait for all promises in the queue to resolve or for timeout to expire, whichever comes first.
  54. *
  55. * @param timeout The time, in ms, after which to resolve to `false` if the queue is still non-empty. Passing `0` (or
  56. * not passing anything) will make the promise wait as long as it takes for the queue to drain before resolving to
  57. * `true`.
  58. * @returns A promise which will resolve to `true` if the queue is already empty or drains before the timeout, and
  59. * `false` otherwise
  60. */
  61. function drain(timeout) {
  62. return new SyncPromise((resolve, reject) => {
  63. let counter = buffer.length;
  64. if (!counter) {
  65. return resolve(true);
  66. }
  67. // wait for `timeout` ms and then resolve to `false` (if not cancelled first)
  68. const capturedSetTimeout = setTimeout(() => {
  69. if (timeout && timeout > 0) {
  70. resolve(false);
  71. }
  72. }, timeout);
  73. // if all promises resolve in time, cancel the timer and resolve to `true`
  74. buffer.forEach(item => {
  75. void resolvedSyncPromise(item).then(() => {
  76. if (!--counter) {
  77. clearTimeout(capturedSetTimeout);
  78. resolve(true);
  79. }
  80. }, reject);
  81. });
  82. });
  83. }
  84. return {
  85. $: buffer,
  86. add,
  87. drain,
  88. };
  89. }
  90. export { makePromiseBuffer };
  91. //# sourceMappingURL=promisebuffer.js.map