promise.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. class TeePromise {
  20. static STATUS_PENDING = Symbol('pending');
  21. static STATUS_RUNNING = {};
  22. static STATUS_DONE = Symbol('done');
  23. constructor () {
  24. this.status_ = this.constructor.STATUS_PENDING;
  25. this.donePromise = new Promise((resolve, reject) => {
  26. this.doneResolve = resolve;
  27. this.doneReject = reject;
  28. });
  29. }
  30. get status () {
  31. return this.status_;
  32. }
  33. set status (status) {
  34. this.status_ = status;
  35. if ( status === this.constructor.STATUS_DONE ) {
  36. this.doneResolve();
  37. }
  38. }
  39. resolve (value) {
  40. this.status_ = this.constructor.STATUS_DONE;
  41. this.doneResolve(value);
  42. }
  43. awaitDone () {
  44. return this.donePromise;
  45. }
  46. then (fn, ...a) {
  47. return this.donePromise.then(fn, ...a);
  48. }
  49. reject (err) {
  50. this.status_ = this.constructor.STATUS_DONE;
  51. this.doneReject(err);
  52. }
  53. /**
  54. * @deprecated use then() instead
  55. */
  56. onComplete(fn) {
  57. return this.then(fn);
  58. }
  59. }
  60. class Lock {
  61. constructor() {
  62. this._locked = false;
  63. this._waiting = [];
  64. }
  65. async acquire(callback) {
  66. await new Promise(resolve => {
  67. if ( ! this._locked ) {
  68. this._locked = true;
  69. resolve();
  70. } else {
  71. this._waiting.push({
  72. resolve,
  73. });
  74. }
  75. })
  76. if ( callback ) {
  77. let retval;
  78. try {
  79. retval = await callback();
  80. } finally {
  81. this.release();
  82. }
  83. return retval;
  84. }
  85. }
  86. release() {
  87. if (this._waiting.length > 0) {
  88. const { resolve } = this._waiting.shift();
  89. resolve();
  90. } else {
  91. this._locked = false;
  92. }
  93. }
  94. }
  95. /**
  96. * @callback behindScheduleCallback
  97. * @param {number} drift - The number of milliseconds that the callback was
  98. * called behind schedule.
  99. * @returns {boolean} - If the callback returns true, the timer will be
  100. * cancelled.
  101. */
  102. /**
  103. * When passing an async callback to setInterval, it's possible for the
  104. * callback to be called again before the previous invocation has finished.
  105. *
  106. * This function wraps setInterval and ensures that the callback is not
  107. * called again until the previous invocation has finished.
  108. *
  109. * @param {Function} callback - The function to call when the timer elapses.
  110. * @param {number} delay - The minimum number of milliseconds between invocations.
  111. * @param {?Array<any>} args - Additional arguments to pass to setInterval.
  112. * @param {?Object} options - Additional options.
  113. * @param {behindScheduleCallback} options.onBehindSchedule - A callback to call when the callback is called behind schedule.
  114. */
  115. const asyncSafeSetInterval = async (callback, delay, args, options) => {
  116. args = args ?? [];
  117. options = options ?? {};
  118. const { onBehindSchedule } = options;
  119. const sleep = (ms) => new Promise(rslv => setTimeout(rslv, ms));
  120. for ( ;; ) {
  121. await sleep(delay);
  122. const ts_start = Date.now();
  123. await callback(...args);
  124. const ts_end = Date.now();
  125. const runtime = ts_end - ts_start;
  126. const sleep_time = delay - runtime;
  127. if ( sleep_time < 0 ) {
  128. if ( onBehindSchedule ) {
  129. const cancel = await onBehindSchedule(-sleep_time);
  130. if ( cancel ) {
  131. return;
  132. }
  133. }
  134. } else {
  135. await sleep(sleep_time);
  136. }
  137. }
  138. }
  139. /**
  140. * raceCase is like Promise.race except it takes an object instead of
  141. * an array, and returns the key of the promise that resolves first
  142. * as well as the value that it resolved to.
  143. *
  144. * @param {Object.<string, Promise>} promise_map
  145. *
  146. * @returns {Promise.<[string, any]>}
  147. */
  148. const raceCase = async (promise_map) => {
  149. return Promise.race(Object.entries(promise_map).map(
  150. ([key, promise]) => promise.then(value => [key, value])));
  151. };
  152. module.exports = {
  153. TeePromise,
  154. Lock,
  155. asyncSafeSetInterval,
  156. raceCase,
  157. };