promisebuffer.js 3.5 KB

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