|
9 | 9 | const { |
10 | 10 | ArrayIsArray, |
11 | 11 | ArrayPrototypePush, |
| 12 | + ArrayPrototypeShift, |
12 | 13 | MathMax, |
13 | 14 | PromisePrototypeThen, |
14 | 15 | PromiseReject, |
@@ -146,6 +147,7 @@ class BroadcastImpl { |
146 | 147 | cursor: this.#bufferStart, |
147 | 148 | resolve: null, |
148 | 149 | reject: null, |
| 150 | + pending: [], |
149 | 151 | detached: false, |
150 | 152 | }; |
151 | 153 |
|
@@ -165,9 +167,10 @@ class BroadcastImpl { |
165 | 167 |
|
166 | 168 | function detach() { |
167 | 169 | state.detached = true; |
168 | | - state.resolve?.({ __proto__: null, done: true, value: undefined }); |
169 | | - state.resolve = null; |
170 | | - state.reject = null; |
| 170 | + if (state.resolve) { |
| 171 | + state.resolve({ __proto__: null, done: true, value: undefined }); |
| 172 | + } |
| 173 | + self.#resolvePendingDone(state); |
171 | 174 | if (self.#deleteConsumer(state)) { |
172 | 175 | self.#tryTrimBuffer(); |
173 | 176 | } |
@@ -208,6 +211,13 @@ class BroadcastImpl { |
208 | 211 | return kDone; |
209 | 212 | } |
210 | 213 |
|
| 214 | + if (state.resolve) { |
| 215 | + const { promise, resolve, reject } = PromiseWithResolvers(); |
| 216 | + ArrayPrototypePush(state.pending, |
| 217 | + { __proto__: null, resolve, reject }); |
| 218 | + return promise; |
| 219 | + } |
| 220 | + |
211 | 221 | const { promise, resolve, reject } = PromiseWithResolvers(); |
212 | 222 | state.resolve = resolve; |
213 | 223 | state.reject = reject; |
@@ -251,6 +261,11 @@ class BroadcastImpl { |
251 | 261 | consumer.resolve = null; |
252 | 262 | consumer.reject = null; |
253 | 263 | } |
| 264 | + if (reason !== undefined) { |
| 265 | + this.#rejectPending(consumer, reason); |
| 266 | + } else { |
| 267 | + this.#resolvePendingDone(consumer); |
| 268 | + } |
254 | 269 | consumer.detached = true; |
255 | 270 | } |
256 | 271 | this.#consumers.clear(); |
@@ -297,7 +312,7 @@ class BroadcastImpl { |
297 | 312 | this.#ended = true; |
298 | 313 |
|
299 | 314 | for (const consumer of this.#consumers) { |
300 | | - if (consumer.resolve) { |
| 315 | + while (consumer.resolve) { |
301 | 316 | const bufferIndex = consumer.cursor - this.#bufferStart; |
302 | 317 | if (bufferIndex < this.#buffer.length) { |
303 | 318 | const chunk = this.#buffer.get(bufferIndex); |
@@ -310,9 +325,15 @@ class BroadcastImpl { |
310 | 325 | consumer.resolve({ __proto__: null, done: false, value: chunk }); |
311 | 326 | } else { |
312 | 327 | consumer.resolve({ __proto__: null, done: true, value: undefined }); |
| 328 | + this.#resolvePendingDone(consumer); |
| 329 | + consumer.detached = true; |
313 | 330 | } |
314 | 331 | consumer.resolve = null; |
315 | 332 | consumer.reject = null; |
| 333 | + if (consumer.detached && this.#deleteConsumer(consumer)) { |
| 334 | + this.#tryTrimBuffer(); |
| 335 | + break; |
| 336 | + } |
316 | 337 | } |
317 | 338 | } |
318 | 339 | } |
@@ -329,6 +350,7 @@ class BroadcastImpl { |
329 | 350 | consumer.resolve = null; |
330 | 351 | consumer.reject = null; |
331 | 352 | } |
| 353 | + this.#rejectPending(consumer, reason); |
332 | 354 | consumer.detached = true; |
333 | 355 | } |
334 | 356 | this.#consumers.clear(); |
@@ -397,6 +419,11 @@ class BroadcastImpl { |
397 | 419 | consumer.resolve = null; |
398 | 420 | consumer.reject = null; |
399 | 421 | resolve({ __proto__: null, done: false, value: chunk }); |
| 422 | + if (consumer.detached && this.#deleteConsumer(consumer)) { |
| 423 | + this.#tryTrimBuffer(); |
| 424 | + } else if (this.#promotePending(consumer)) { |
| 425 | + ArrayPrototypePush(this.#waiters, consumer); |
| 426 | + } |
400 | 427 | } else { |
401 | 428 | // Still waiting -- put back |
402 | 429 | ArrayPrototypePush(this.#waiters, consumer); |
@@ -419,6 +446,31 @@ class BroadcastImpl { |
419 | 446 | } |
420 | 447 | return false; |
421 | 448 | } |
| 449 | + |
| 450 | + #promotePending(consumer) { |
| 451 | + const next = ArrayPrototypeShift(consumer.pending); |
| 452 | + if (next === undefined) return false; |
| 453 | + consumer.resolve = next.resolve; |
| 454 | + consumer.reject = next.reject; |
| 455 | + return true; |
| 456 | + } |
| 457 | + |
| 458 | + #resolvePendingDone(consumer) { |
| 459 | + if (consumer.resolve) { |
| 460 | + consumer.resolve = null; |
| 461 | + consumer.reject = null; |
| 462 | + } |
| 463 | + while (consumer.pending.length > 0) { |
| 464 | + ArrayPrototypeShift(consumer.pending).resolve( |
| 465 | + { __proto__: null, done: true, value: undefined }); |
| 466 | + } |
| 467 | + } |
| 468 | + |
| 469 | + #rejectPending(consumer, reason) { |
| 470 | + while (consumer.pending.length > 0) { |
| 471 | + ArrayPrototypeShift(consumer.pending).reject(reason); |
| 472 | + } |
| 473 | + } |
422 | 474 | } |
423 | 475 |
|
424 | 476 | // ============================================================================= |
|