| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,8 @@ | |||
| 6 | 6 | const { | |
| 7 | 7 | Promise, | |
| 8 | 8 | PromisePrototypeThen, | |
| 9 | + ReflectApply, | ||
| 10 | + Symbol, | ||
| 9 | 11 | SymbolDispose, | |
| 10 | 12 | } = primordials; | |
| 11 | 13 | ||
@@ -62,6 +64,50 @@ function bindAsyncResource(fn, type) { | |||
| 62 | 64 | }; | |
| 63 | 65 | } | |
| 64 | 66 | ||
| 67 | + /** | ||
| 68 | + * Returns the current stream error tracked by eos(), if any. | ||
| 69 | + * @param {import('stream').Stream} stream | ||
| 70 | + * @returns {Error | null} | ||
| 71 | + */ | ||
| 72 | + function getEosErrored(stream) { | ||
| 73 | + const errored = isWritableErrored(stream) || isReadableErrored(stream); | ||
| 74 | + return typeof errored !== 'boolean' && errored || null; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * Returns the error eos() would report from an immediate close, including | ||
| 79 | + * premature close detection for unfinished readable or writable sides. | ||
| 80 | + * @param {import('stream').Stream} stream | ||
| 81 | + * @param {boolean} readable | ||
| 82 | + * @param {boolean | null} readableFinished | ||
| 83 | + * @param {boolean} writable | ||
| 84 | + * @param {boolean | null} writableFinished | ||
| 85 | + * @returns {Error | null} | ||
| 86 | + */ | ||
| 87 | + function getEosOnCloseError(stream, readable, readableFinished, writable, writableFinished) { | ||
| 88 | + const errored = getEosErrored(stream); | ||
| 89 | + if (errored) { | ||
| 90 | + return errored; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + if (readable && !readableFinished && isReadableNodeStream(stream, true)) { | ||
| 94 | + if (!isReadableFinished(stream, false)) { | ||
| 95 | + return new ERR_STREAM_PREMATURE_CLOSE(); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + if (writable && !writableFinished) { | ||
| 99 | + if (!isWritableFinished(stream, false)) { | ||
| 100 | + return new ERR_STREAM_PREMATURE_CLOSE(); | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + return null; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + // Internal only: if eos() can settle immediately, invoke the callback before | ||
| 108 | + // returning cleanup. Callers must tolerate cleanup yet to be assigned. | ||
| 109 | + const kEosNodeSynchronousCallback = Symbol('kEosNodeSynchronousCallback'); | ||
| 110 | + | ||
| 65 | 111 | function eos(stream, options, callback) { | |
| 66 | 112 | if (arguments.length === 2) { | |
| 67 | 113 | callback = options; | |
@@ -74,10 +120,6 @@ function eos(stream, options, callback) { | |||
| 74 | 120 | validateFunction(callback, 'callback'); | |
| 75 | 121 | validateAbortSignal(options.signal, 'options.signal'); | |
| 76 | 122 | ||
| 77 | - // Avoid AsyncResource.bind() because it calls ObjectDefineProperties which | ||
| 78 | - // is a bottleneck here. | ||
| 79 | - callback = once(bindAsyncResource(callback, 'STREAM_END_OF_STREAM')); | ||
| 80 | - | ||
| 81 | 123 | if (isReadableStream(stream) || isWritableStream(stream)) { | |
| 82 | 124 | return eosWeb(stream, options, callback); | |
| 83 | 125 | } | |
@@ -89,15 +131,6 @@ function eos(stream, options, callback) { | |||
| 89 | 131 | const readable = options.readable ?? isReadableNodeStream(stream); | |
| 90 | 132 | const writable = options.writable ?? isWritableNodeStream(stream); | |
| 91 | 133 | ||
| 92 | - const wState = stream._writableState; | ||
| 93 | - const rState = stream._readableState; | ||
| 94 | - | ||
| 95 | - const onlegacyfinish = () => { | ||
| 96 | - if (!stream.writable) { | ||
| 97 | - onfinish(); | ||
| 98 | - } | ||
| 99 | - }; | ||
| 100 | - | ||
| 101 | 134 | // TODO (ronag): Improve soft detection to include core modules and | |
| 102 | 135 | // common ecosystem modules that do properly emit 'close' but fail | |
| 103 | 136 | // this generic check. | |
@@ -106,8 +139,83 @@ function eos(stream, options, callback) { | |||
| 106 | 139 | isReadableNodeStream(stream) === readable && | |
| 107 | 140 | isWritableNodeStream(stream) === writable | |
| 108 | 141 | ); | |
| 109 | - | ||
| 110 | 142 | let writableFinished = isWritableFinished(stream, false); | |
| 143 | + let readableFinished = isReadableFinished(stream, false); | ||
| 144 | + | ||
| 145 | + const wState = stream._writableState; | ||
| 146 | + const rState = stream._readableState; | ||
| 147 | + | ||
| 148 | + /** | ||
| 149 | + * @type {Error | null | undefined} | ||
| 150 | + * undefined: to be determined | ||
| 151 | + * null: no error | ||
| 152 | + * Error: an error occurred | ||
| 153 | + */ | ||
| 154 | + let immediateResult; | ||
| 155 | + if (isClosed(stream)) { | ||
| 156 | + immediateResult = getEosOnCloseError( | ||
| 157 | + stream, | ||
| 158 | + readable, | ||
| 159 | + readableFinished, | ||
| 160 | + writable, | ||
| 161 | + writableFinished, | ||
| 162 | + ); | ||
| 163 | + } else if (wState?.errorEmitted || rState?.errorEmitted) { | ||
| 164 | + if (!willEmitClose) { | ||
| 165 | + immediateResult = getEosErrored(stream); | ||
| 166 | + } | ||
| 167 | + } else if ( | ||
| 168 | + !readable && | ||
| 169 | + (!willEmitClose || isReadable(stream)) && | ||
| 170 | + (writableFinished || isWritable(stream) === false) && | ||
| 171 | + (wState == null || wState.pendingcb === undefined || wState.pendingcb === 0) | ||
| 172 | + ) { | ||
| 173 | + immediateResult = getEosErrored(stream); | ||
| 174 | + } else if ( | ||
| 175 | + !writable && | ||
| 176 | + (!willEmitClose || isWritable(stream)) && | ||
| 177 | + (readableFinished || isReadable(stream) === false) | ||
| 178 | + ) { | ||
| 179 | + immediateResult = getEosErrored(stream); | ||
| 180 | + } else if ((rState && stream.req && stream.aborted)) { | ||
| 181 | + immediateResult = getEosErrored(stream); | ||
| 182 | + } | ||
| 183 | + let cleanup = () => { | ||
| 184 | + callback = nop; | ||
| 185 | + }; | ||
| 186 | + if (immediateResult !== undefined) { | ||
| 187 | + if (options.error !== false) { | ||
| 188 | + stream.on('error', nop); | ||
| 189 | + cleanup = () => { | ||
| 190 | + callback = nop; | ||
| 191 | + stream.removeListener('error', nop); | ||
| 192 | + }; | ||
| 193 | + } | ||
| 194 | + } else if (options.signal?.aborted) { | ||
| 195 | + immediateResult = new AbortError(undefined, { cause: options.signal.reason }); | ||
| 196 | + } | ||
| 197 | + if (immediateResult !== undefined && options[kEosNodeSynchronousCallback]) { | ||
| 198 | + ReflectApply(callback, stream, immediateResult === null ? [] : [immediateResult]); | ||
| 199 | + return cleanup; | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + // Avoid AsyncResource.bind() because it calls ObjectDefineProperties which | ||
| 203 | + // is a bottleneck here. | ||
| 204 | + callback = bindAsyncResource(callback, 'STREAM_END_OF_STREAM'); | ||
| 205 | + | ||
| 206 | + if (immediateResult !== undefined) { | ||
| 207 | + process.nextTick(() => ReflectApply(callback, stream, immediateResult === null ? [] : [immediateResult])); | ||
| 208 | + return cleanup; | ||
| 209 | + } | ||
| 210 | + | ||
| 211 | + callback = once(callback); | ||
| 212 | + | ||
| 213 | + const onlegacyfinish = () => { | ||
| 214 | + if (!stream.writable) { | ||
| 215 | + onfinish(); | ||
| 216 | + } | ||
| 217 | + }; | ||
| 218 | + | ||
| 111 | 219 | const onfinish = () => { | |
| 112 | 220 | writableFinished = true; | |
| 113 | 221 | // Stream should not be destroyed here. If it is that | |
@@ -126,7 +234,6 @@ function eos(stream, options, callback) { | |||
| 126 | 234 | } | |
| 127 | 235 | }; | |
| 128 | 236 | ||
| 129 | - let readableFinished = isReadableFinished(stream, false); | ||
| 130 | 237 | const onend = () => { | |
| 131 | 238 | readableFinished = true; | |
| 132 | 239 | // Stream should not be destroyed here. If it is that | |
@@ -149,41 +256,13 @@ function eos(stream, options, callback) { | |||
| 149 | 256 | callback.call(stream, err); | |
| 150 | 257 | }; | |
| 151 | 258 | ||
| 152 | - let closed = isClosed(stream); | ||
| 153 | - | ||
| 154 | 259 | const onclose = () => { | |
| 155 | - closed = true; | ||
| 156 | - | ||
| 157 | - const errored = isWritableErrored(stream) || isReadableErrored(stream); | ||
| 158 | - | ||
| 159 | - if (errored && typeof errored !== 'boolean') { | ||
| 160 | - return callback.call(stream, errored); | ||
| 161 | - } | ||
| 162 | - | ||
| 163 | - if (readable && !readableFinished && isReadableNodeStream(stream, true)) { | ||
| 164 | - if (!isReadableFinished(stream, false)) | ||
| 165 | - return callback.call(stream, | ||
| 166 | - new ERR_STREAM_PREMATURE_CLOSE()); | ||
| 167 | - } | ||
| 168 | - if (writable && !writableFinished) { | ||
| 169 | - if (!isWritableFinished(stream, false)) | ||
| 170 | - return callback.call(stream, | ||
| 171 | - new ERR_STREAM_PREMATURE_CLOSE()); | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - callback.call(stream); | ||
| 175 | - }; | ||
| 176 | - | ||
| 177 | - const onclosed = () => { | ||
| 178 | - closed = true; | ||
| 179 | - | ||
| 180 | - const errored = isWritableErrored(stream) || isReadableErrored(stream); | ||
| 181 | - | ||
| 182 | - if (errored && typeof errored !== 'boolean') { | ||
| 183 | - return callback.call(stream, errored); | ||
| 260 | + const error = getEosOnCloseError(stream, readable, readableFinished, writable, writableFinished); | ||
| 261 | + if (error === null) { | ||
| 262 | + callback.call(stream); | ||
| 263 | + } else { | ||
| 264 | + callback.call(stream, error); | ||
| 184 | 265 | } | |
| 185 | - | ||
| 186 | - callback.call(stream); | ||
| 187 | 266 | }; | |
| 188 | 267 | ||
| 189 | 268 | const onrequest = () => { | |
@@ -217,30 +296,7 @@ function eos(stream, options, callback) { | |||
| 217 | 296 | } | |
| 218 | 297 | stream.on('close', onclose); | |
| 219 | 298 | ||
| 220 | - if (closed) { | ||
| 221 | - process.nextTick(onclose); | ||
| 222 | - } else if (wState?.errorEmitted || rState?.errorEmitted) { | ||
| 223 | - if (!willEmitClose) { | ||
| 224 | - process.nextTick(onclosed); | ||
| 225 | - } | ||
| 226 | - } else if ( | ||
| 227 | - !readable && | ||
| 228 | - (!willEmitClose || isReadable(stream)) && | ||
| 229 | - (writableFinished || isWritable(stream) === false) && | ||
| 230 | - (wState == null || wState.pendingcb === undefined || wState.pendingcb === 0) | ||
| 231 | - ) { | ||
| 232 | - process.nextTick(onclosed); | ||
| 233 | - } else if ( | ||
| 234 | - !writable && | ||
| 235 | - (!willEmitClose || isWritable(stream)) && | ||
| 236 | - (readableFinished || isReadable(stream) === false) | ||
| 237 | - ) { | ||
| 238 | - process.nextTick(onclosed); | ||
| 239 | - } else if ((rState && stream.req && stream.aborted)) { | ||
| 240 | - process.nextTick(onclosed); | ||
| 241 | - } | ||
| 242 | - | ||
| 243 | - const cleanup = () => { | ||
| 299 | + cleanup = () => { | ||
| 244 | 300 | callback = nop; | |
| 245 | 301 | stream.removeListener('aborted', onclose); | |
| 246 | 302 | stream.removeListener('complete', onfinish); | |
@@ -255,7 +311,7 @@ function eos(stream, options, callback) { | |||
| 255 | 311 | stream.removeListener('close', onclose); | |
| 256 | 312 | }; | |
| 257 | 313 | ||
| 258 | - if (options.signal && !closed) { | ||
| 314 | + if (options.signal) { | ||
| 259 | 315 | const abort = () => { | |
| 260 | 316 | // Keep it because cleanup removes it. | |
| 261 | 317 | const endCallback = callback; | |
@@ -264,23 +320,23 @@ function eos(stream, options, callback) { | |||
| 264 | 320 | stream, | |
| 265 | 321 | new AbortError(undefined, { cause: options.signal.reason })); | |
| 266 | 322 | }; | |
| 267 | - if (options.signal.aborted) { | ||
| 268 | - process.nextTick(abort); | ||
| 269 | - } else { | ||
| 270 | - addAbortListener ??= require('internal/events/abort_listener').addAbortListener; | ||
| 271 | - const disposable = addAbortListener(options.signal, abort); | ||
| 272 | - const originalCallback = callback; | ||
| 273 | - callback = once((...args) => { | ||
| 274 | - disposable[SymbolDispose](); | ||
| 275 | - originalCallback.apply(stream, args); | ||
| 276 | - }); | ||
| 277 | - } | ||
| 323 | + addAbortListener ??= require('internal/events/abort_listener').addAbortListener; | ||
| 324 | + const disposable = addAbortListener(options.signal, abort); | ||
| 325 | + const originalCallback = callback; | ||
| 326 | + callback = once((...args) => { | ||
| 327 | + disposable[SymbolDispose](); | ||
| 328 | + ReflectApply(originalCallback, stream, args); | ||
| 329 | + }); | ||
| 278 | 330 | } | |
| 279 | 331 | ||
| 280 | 332 | return cleanup; | |
| 281 | 333 | } | |
| 282 | 334 | ||
| 283 | 335 | function eosWeb(stream, options, callback) { | |
| 336 | + // Avoid AsyncResource.bind() because it calls ObjectDefineProperties which | ||
| 337 | + // is a bottleneck here. | ||
| 338 | + callback = once(bindAsyncResource(callback, 'STREAM_END_OF_STREAM')); | ||
| 339 | + | ||
| 284 | 340 | let isAborted = false; | |
| 285 | 341 | let abort = nop; | |
| 286 | 342 | if (options.signal) { | |
@@ -339,4 +395,5 @@ function finished(stream, opts) { | |||
| 339 | 395 | module.exports = { | |
| 340 | 396 | eos, | |
| 341 | 397 | finished, | |
| 398 | + kEosNodeSynchronousCallback, | ||
| 342 | 399 | }; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments