| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 562168f commit f7e0c81
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -101,6 +101,7 @@ const { | |||
| 101 | 101 | cloneAsUint8Array, | |
| 102 | 102 | copyArrayBuffer, | |
| 103 | 103 | createPromiseCallback1Param, | |
| 104 | + createRawCallback1Param, | ||
| 104 | 105 | customInspect, | |
| 105 | 106 | defaultSizeAlgorithm, | |
| 106 | 107 | dequeueValue, | |
@@ -110,6 +111,7 @@ const { | |||
| 110 | 111 | getNonWritablePropertyDescriptor, | |
| 111 | 112 | isBrandCheck, | |
| 112 | 113 | kEmptyQueue, | |
| 114 | + kResolvedPromise, | ||
| 113 | 115 | kState, | |
| 114 | 116 | kType, | |
| 115 | 117 | lazyTransfer, | |
@@ -121,6 +123,7 @@ const { | |||
| 121 | 123 | resetQueue, | |
| 122 | 124 | resolvedRecord, | |
| 123 | 125 | setPromiseHandled, | |
| 126 | + thenAlgorithmResult, | ||
| 124 | 127 | } = require('internal/webstreams/util'); | |
| 125 | 128 | ||
| 126 | 129 | const { | |
@@ -137,7 +140,6 @@ const { | |||
| 137 | 140 | writableStreamDefaultWriterRelease, | |
| 138 | 141 | writableStreamDefaultWriterWriteWithRequest, | |
| 139 | 142 | writerClosedPromise, | |
| 140 | - writerReadyPromise, | ||
| 141 | 143 | } = require('internal/webstreams/writablestream'); | |
| 142 | 144 | ||
| 143 | 145 | const { Buffer } = require('buffer'); | |
@@ -1664,11 +1666,31 @@ function readableStreamPipeTo( | |||
| 1664 | 1666 | // the chunk travels through `pendingChunk`. | |
| 1665 | 1667 | let pendingChunk; | |
| 1666 | 1668 | let readRequest; | |
| 1669 | + let readyHook; | ||
| 1667 | 1670 | ||
| 1668 | 1671 | // Ready promise rejection is handled by the destination-errored | |
| 1669 | 1672 | // watcher. | |
| 1670 | 1673 | function ignoreReadyRejection() {} | |
| 1671 | 1674 | ||
| 1675 | + // Parks the pump on the destination's backpressure by installing a | ||
| 1676 | + // record that duck-types the writer's lazily-materialized | ||
| 1677 | + // [[readyPromise]] record: writableStreamUpdateBackpressure resolves it | ||
| 1678 | + // when backpressure clears (after publishing the new backpressure | ||
| 1679 | + // state), which re-enters the pump directly instead of rotating a | ||
| 1680 | + // fresh promise record plus reaction per flip. The pipe holds the only | ||
| 1681 | + // reference to the writer, so the record is never observable as a real | ||
| 1682 | + // ready promise; the erroring/release paths probe `promise` via | ||
| 1683 | + // isPromisePending() and call `reject`, so it carries a real | ||
| 1684 | + // forever-pending promise and a no-op reject. | ||
| 1685 | + function parkOnReady() { | ||
| 1686 | + readyHook ??= { | ||
| 1687 | + promise: new Promise(nonOpCallback), | ||
| 1688 | + resolve: pump, | ||
| 1689 | + reject: ignoreReadyRejection, | ||
| 1690 | + }; | ||
| 1691 | + writer[kState].ready = readyHook; | ||
| 1692 | + } | ||
| 1693 | + | ||
| 1672 | 1694 | function forwardChunk() { | |
| 1673 | 1695 | const chunk = pendingChunk; | |
| 1674 | 1696 | pendingChunk = undefined; | |
@@ -1680,10 +1702,7 @@ function readableStreamPipeTo( | |||
| 1680 | 1702 | if (shuttingDown) return; | |
| 1681 | 1703 | ||
| 1682 | 1704 | if (dest[kState].backpressure) { | |
| 1683 | - PromisePrototypeThen( | ||
| 1684 | - writerReadyPromise(writer).promise, | ||
| 1685 | - pump, | ||
| 1686 | - ignoreReadyRejection); | ||
| 1705 | + parkOnReady(); | ||
| 1687 | 1706 | return; | |
| 1688 | 1707 | } | |
| 1689 | 1708 | ||
@@ -1728,9 +1747,18 @@ function readableStreamPipeTo( | |||
| 1728 | 1747 | return; | |
| 1729 | 1748 | } | |
| 1730 | 1749 | ||
| 1731 | - // Yield to microtask queue between batches to allow events/signals | ||
| 1732 | - // to fire | ||
| 1733 | - queueMicrotask(pump); | ||
| 1750 | + // Park on backpressure directly: the ready hook resumes the pump | ||
| 1751 | + // when a completed write clears it. | ||
| 1752 | + if (dest[kState].backpressure) { | ||
| 1753 | + parkOnReady(); | ||
| 1754 | + return; | ||
| 1755 | + } | ||
| 1756 | + | ||
| 1757 | + // Yield to the microtask queue between batches so completed-write | ||
| 1758 | + // reactions and events/signals fire; a shared resolved promise | ||
| 1759 | + // enqueues the continuation at the same position as queueMicrotask | ||
| 1760 | + // without the per-batch scheduling overhead. | ||
| 1761 | + PromisePrototypeThen(kResolvedPromise, pump); | ||
| 1734 | 1762 | return; | |
| 1735 | 1763 | } | |
| 1736 | 1764 | ||
@@ -1742,7 +1770,7 @@ function readableStreamPipeTo( | |||
| 1742 | 1770 | // synchronous write during enqueue(). See WHATWG Streams spec | |
| 1743 | 1771 | // "ReadableStreamPipeTo" step 15's "chunk steps". | |
| 1744 | 1772 | pendingChunk = chunk; | |
| 1745 | - queueMicrotask(forwardChunk); | ||
| 1773 | + PromisePrototypeThen(kResolvedPromise, forwardChunk); | ||
| 1746 | 1774 | }, | |
| 1747 | 1775 | [kClose]() {}, | |
| 1748 | 1776 | [kError]() {}, | |
@@ -1857,7 +1885,7 @@ function readableStreamDefaultTee(stream, cloneForBranch2) { | |||
| 1857 | 1885 | // The microtask is required by the spec (ReadableStreamTee's | |
| 1858 | 1886 | // "chunk steps" queue one). | |
| 1859 | 1887 | pendingChunk = value; | |
| 1860 | - queueMicrotask(forwardChunk); | ||
| 1888 | + PromisePrototypeThen(kResolvedPromise, forwardChunk); | ||
| 1861 | 1889 | }, | |
| 1862 | 1890 | [kClose]() { | |
| 1863 | 1891 | // The `process.nextTick()` is not part of the spec. | |
@@ -2010,7 +2038,7 @@ function readableByteStreamTee(stream) { | |||
| 2010 | 2038 | defaultReadRequest ??= { | |
| 2011 | 2039 | [kChunk](chunk) { | |
| 2012 | 2040 | pendingChunk = chunk; | |
| 2013 | - queueMicrotask(forwardChunk); | ||
| 2041 | + PromisePrototypeThen(kResolvedPromise, forwardChunk); | ||
| 2014 | 2042 | }, | |
| 2015 | 2043 | [kClose]() { | |
| 2016 | 2044 | reading = false; | |
@@ -2699,8 +2727,18 @@ function readableStreamDefaultControllerPull(controller) { | |||
| 2699 | 2727 | controller[kState].pullRejected = | |
| 2700 | 2728 | (error) => readableStreamDefaultControllerError(controller, error); | |
| 2701 | 2729 | } | |
| 2702 | - PromisePrototypeThen( | ||
| 2703 | - controller[kState].pullAlgorithm(controller), | ||
| 2730 | + // The pull algorithm may be a raw callback (a wrapped user source.pull | ||
| 2731 | + // returns its result uncoerced; a synchronous throw surfaces here) or an | ||
| 2732 | + // internal algorithm that always returns a promise; thenAlgorithmResult | ||
| 2733 | + // handles both. | ||
| 2734 | + let result; | ||
| 2735 | + try { | ||
| 2736 | + result = controller[kState].pullAlgorithm(controller); | ||
| 2737 | + } catch (error) { | ||
| 2738 | + result = PromiseReject(error); | ||
| 2739 | + } | ||
| 2740 | + thenAlgorithmResult( | ||
| 2741 | + result, | ||
| 2704 | 2742 | controller[kState].pullFulfilled, | |
| 2705 | 2743 | controller[kState].pullRejected); | |
| 2706 | 2744 | } | |
@@ -2826,7 +2864,7 @@ function setupReadableStreamDefaultControllerFromSource( | |||
| 2826 | 2864 | FunctionPrototypeBind(start, source, controller) : | |
| 2827 | 2865 | nonOpStart; | |
| 2828 | 2866 | const pullAlgorithm = pull ? | |
| 2829 | - createPromiseCallback1Param('source.pull', pull, source) : | ||
| 2867 | + createRawCallback1Param('source.pull', pull, source) : | ||
| 2830 | 2868 | nonOpPull; | |
| 2831 | 2869 | const cancelAlgorithm = cancel ? | |
| 2832 | 2870 | createPromiseCallback1Param('source.cancel', cancel, source) : | |
@@ -3519,8 +3557,15 @@ function readableByteStreamControllerCallPullIfNeeded(controller) { | |||
| 3519 | 3557 | controller[kState].pullRejected = | |
| 3520 | 3558 | (error) => readableByteStreamControllerError(controller, error); | |
| 3521 | 3559 | } | |
| 3522 | - PromisePrototypeThen( | ||
| 3523 | - controller[kState].pullAlgorithm(controller), | ||
| 3560 | + // See readableStreamDefaultControllerPull for the raw-callback contract. | ||
| 3561 | + let result; | ||
| 3562 | + try { | ||
| 3563 | + result = controller[kState].pullAlgorithm(controller); | ||
| 3564 | + } catch (error) { | ||
| 3565 | + result = PromiseReject(error); | ||
| 3566 | + } | ||
| 3567 | + thenAlgorithmResult( | ||
| 3568 | + result, | ||
| 3524 | 3569 | controller[kState].pullFulfilled, | |
| 3525 | 3570 | controller[kState].pullRejected); | |
| 3526 | 3571 | } | |
@@ -3700,7 +3745,7 @@ function setupReadableByteStreamControllerFromSource( | |||
| 3700 | 3745 | FunctionPrototypeBind(start, source, controller) : | |
| 3701 | 3746 | nonOpStart; | |
| 3702 | 3747 | const pullAlgorithm = pull ? | |
| 3703 | - createPromiseCallback1Param('source.pull', pull, source) : | ||
| 3748 | + createRawCallback1Param('source.pull', pull, source) : | ||
| 3704 | 3749 | nonOpPull; | |
| 3705 | 3750 | const cancelAlgorithm = cancel ? | |
| 3706 | 3751 | createPromiseCallback1Param('source.cancel', cancel, source) : | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -338,6 +338,40 @@ function createPromiseCallbackNoParams(name, fn, thisArg) { | |||
| 338 | 338 | return async () => FunctionPrototypeCall(fn, thisArg); | |
| 339 | 339 | } | |
| 340 | 340 | ||
| 341 | + // Raw variants that skip the async wrapper's implicit result promise. | ||
| 342 | + // Consumers of a raw callback invoke it inside try/catch and route the | ||
| 343 | + // result through thenAlgorithmResult() below. | ||
| 344 | + function createRawCallback1Param(name, fn, thisArg) { | ||
| 345 | + validateFunction(fn, name); | ||
| 346 | + return (arg) => FunctionPrototypeCall(fn, thisArg, arg); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + function createRawCallback2Params(name, fn, thisArg) { | ||
| 350 | + validateFunction(fn, name); | ||
| 351 | + return (arg1, arg2) => FunctionPrototypeCall(fn, thisArg, arg1, arg2); | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + // A single shared, forever-resolved promise used to enqueue a reaction at | ||
| 355 | + // the next microtask checkpoint without allocating a fresh promise. | ||
| 356 | + const kResolvedPromise = PromiseResolve(); | ||
| 357 | + | ||
| 358 | + // Wires the (possibly non-thenable) result of an underlying algorithm | ||
| 359 | + // callback to its fulfilled/rejected continuations. A non-thenable result | ||
| 360 | + // means fulfillment is guaranteed and no then() lookup is observable, so | ||
| 361 | + // the fulfillment step is enqueued directly at the exact microtask | ||
| 362 | + // position the coerced promise's reaction would have had, skipping the | ||
| 363 | + // per-chunk promise allocation. For thenable results PromiseResolve() | ||
| 364 | + // matches the spec's "a promise resolved with" conversion (identity for | ||
| 365 | + // native promises). | ||
| 366 | + function thenAlgorithmResult(result, onFulfilled, onRejected) { | ||
| 367 | + if (result === null || | ||
| 368 | + (typeof result !== 'object' && typeof result !== 'function')) { | ||
| 369 | + PromisePrototypeThen(kResolvedPromise, onFulfilled); | ||
| 370 | + } else { | ||
| 371 | + PromisePrototypeThen(PromiseResolve(result), onFulfilled, onRejected); | ||
| 372 | + } | ||
| 373 | + } | ||
| 374 | + | ||
| 341 | 375 | function createPromiseCallback1Param(name, fn, thisArg) { | |
| 342 | 376 | validateFunction(fn, name); | |
| 343 | 377 | return async (arg) => FunctionPrototypeCall(fn, thisArg, arg); | |
@@ -386,11 +420,14 @@ async function nonOpFlush() {} | |||
| 386 | 420 | ||
| 387 | 421 | function nonOpStart() {} | |
| 388 | 422 | ||
| 389 | - async function nonOpPull() {} | ||
| 423 | + // nonOpPull and nonOpWrite are raw callbacks (see createRawCallback*): | ||
| 424 | + // their non-thenable return takes the allocation-free fast path in | ||
| 425 | + // thenAlgorithmResult(). | ||
| 426 | + function nonOpPull() {} | ||
| 390 | 427 | ||
| 391 | 428 | async function nonOpCancel() {} | |
| 392 | 429 | ||
| 393 | - async function nonOpWrite() {} | ||
| 430 | + function nonOpWrite() {} | ||
| 394 | 431 | ||
| 395 | 432 | let transfer; | |
| 396 | 433 | function lazyTransfer() { | |
@@ -411,6 +448,8 @@ module.exports = { | |||
| 411 | 448 | createPromiseCallbackNoParams, | |
| 412 | 449 | createPromiseCallback1Param, | |
| 413 | 450 | createPromiseCallback2Params, | |
| 451 | + createRawCallback1Param, | ||
| 452 | + createRawCallback2Params, | ||
| 414 | 453 | customInspect, | |
| 415 | 454 | defaultSizeAlgorithm, | |
| 416 | 455 | dequeueValue, | |
@@ -421,6 +460,7 @@ module.exports = { | |||
| 421 | 460 | isBrandCheck, | |
| 422 | 461 | isPromisePending, | |
| 423 | 462 | kEmptyQueue, | |
| 463 | + kResolvedPromise, | ||
| 424 | 464 | kState, | |
| 425 | 465 | kType, | |
| 426 | 466 | lazyTransfer, | |
@@ -435,4 +475,5 @@ module.exports = { | |||
| 435 | 475 | resetQueue, | |
| 436 | 476 | resolvedRecord, | |
| 437 | 477 | setPromiseHandled, | |
| 478 | + thenAlgorithmResult, | ||
| 438 | 479 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -56,7 +56,7 @@ const { | |||
| 56 | 56 | Queue, | |
| 57 | 57 | createPromiseCallbackNoParams, | |
| 58 | 58 | createPromiseCallback1Param, | |
| 59 | - createPromiseCallback2Params, | ||
| 59 | + createRawCallback2Params, | ||
| 60 | 60 | customInspect, | |
| 61 | 61 | defaultSizeAlgorithm, | |
| 62 | 62 | dequeueValue, | |
@@ -78,6 +78,7 @@ const { | |||
| 78 | 78 | resetQueue, | |
| 79 | 79 | resolvedRecord, | |
| 80 | 80 | setPromiseHandled, | |
| 81 | + thenAlgorithmResult, | ||
| 81 | 82 | } = require('internal/webstreams/util'); | |
| 82 | 83 | ||
| 83 | 84 | const { | |
@@ -769,7 +770,12 @@ function writableStreamUpdateBackpressure(controller, streamState) { | |||
| 769 | 770 | const backpressure = | |
| 770 | 771 | controllerState.highWaterMark - controllerState.queueTotalSize <= 0; | |
| 771 | 772 | const writer = streamState.writer; | |
| 772 | - if (writer !== undefined && streamState.backpressure !== backpressure) { | ||
| 773 | + const changed = streamState.backpressure !== backpressure; | ||
| 774 | + // The state field is published before the ready record is resolved so | ||
| 775 | + // that a ready resolve hook (pipeTo's pump continuation) observes the | ||
| 776 | + // new value. | ||
| 777 | + streamState.backpressure = backpressure; | ||
| 778 | + if (writer !== undefined && changed) { | ||
| 773 | 779 | if (backpressure) { | |
| 774 | 780 | // The spec replaces [[readyPromise]] with a fresh pending promise; | |
| 775 | 781 | // dropping the cache lets the next observation derive it. | |
@@ -778,7 +784,6 @@ function writableStreamUpdateBackpressure(controller, streamState) { | |||
| 778 | 784 | writer[kState].ready?.resolve(); | |
| 779 | 785 | } | |
| 780 | 786 | } | |
| 781 | - streamState.backpressure = backpressure; | ||
| 782 | 787 | } | |
| 783 | 788 | ||
| 784 | 789 | function writableStreamStartErroring(stream, reason) { | |
@@ -1197,8 +1202,18 @@ function writableStreamDefaultControllerProcessWrite(controller, chunk) { | |||
| 1197 | 1202 | }; | |
| 1198 | 1203 | } | |
| 1199 | 1204 | ||
| 1200 | - PromisePrototypeThen( | ||
| 1201 | - writeAlgorithm(chunk, controller), | ||
| 1205 | + // The write algorithm may be a raw callback (a wrapped user sink.write | ||
| 1206 | + // returns its result uncoerced; a synchronous throw surfaces here) or an | ||
| 1207 | + // internal algorithm that always returns a promise; thenAlgorithmResult | ||
| 1208 | + // handles both. | ||
| 1209 | + let result; | ||
| 1210 | + try { | ||
| 1211 | + result = writeAlgorithm(chunk, controller); | ||
| 1212 | + } catch (error) { | ||
| 1213 | + result = PromiseReject(error); | ||
| 1214 | + } | ||
| 1215 | + thenAlgorithmResult( | ||
| 1216 | + result, | ||
| 1202 | 1217 | controller[kState].writeFulfilled, | |
| 1203 | 1218 | controller[kState].writeRejected); | |
| 1204 | 1219 | } | |
@@ -1323,7 +1338,7 @@ function setupWritableStreamDefaultControllerFromSink( | |||
| 1323 | 1338 | FunctionPrototypeBind(start, sink, controller) : | |
| 1324 | 1339 | nonOpStart; | |
| 1325 | 1340 | const writeAlgorithm = write ? | |
| 1326 | - createPromiseCallback2Params('sink.write', write, sink) : | ||
| 1341 | + createRawCallback2Params('sink.write', write, sink) : | ||
| 1327 | 1342 | nonOpWrite; | |
| 1328 | 1343 | const closeAlgorithm = close ? | |
| 1329 | 1344 | createPromiseCallbackNoParams('sink.close', close, sink) : | |
| Back | FazBrowse Home | New Git URL |
0 commit comments