| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 450da99 commit 97f3072
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1886,16 +1886,14 @@ const { pipeline } = require('stream/promises'); | |||
| 1886 | 1886 | ||
| 1887 | 1887 | async function run() { | |
| 1888 | 1888 | const ac = new AbortController(); | |
| 1889 | - const options = { | ||
| 1890 | - signal: ac.signal, | ||
| 1891 | - }; | ||
| 1889 | + const signal = ac.signal; | ||
| 1892 | 1890 | ||
| 1893 | 1891 | setTimeout(() => ac.abort(), 1); | |
| 1894 | 1892 | await pipeline( | |
| 1895 | 1893 | fs.createReadStream('archive.tar'), | |
| 1896 | 1894 | zlib.createGzip(), | |
| 1897 | 1895 | fs.createWriteStream('archive.tar.gz'), | |
| 1898 | - options, | ||
| 1896 | + { signal }, | ||
| 1899 | 1897 | ); | |
| 1900 | 1898 | } | |
| 1901 | 1899 | ||
@@ -1911,10 +1909,10 @@ const fs = require('fs'); | |||
| 1911 | 1909 | async function run() { | |
| 1912 | 1910 | await pipeline( | |
| 1913 | 1911 | fs.createReadStream('lowercase.txt'), | |
| 1914 | - async function* (source) { | ||
| 1912 | + async function* (source, signal) { | ||
| 1915 | 1913 | source.setEncoding('utf8'); // Work with strings rather than `Buffer`s. | |
| 1916 | 1914 | for await (const chunk of source) { | |
| 1917 | - yield chunk.toUpperCase(); | ||
| 1915 | + yield await processChunk(chunk, { signal }); | ||
| 1918 | 1916 | } | |
| 1919 | 1917 | }, | |
| 1920 | 1918 | fs.createWriteStream('uppercase.txt') | |
@@ -1925,6 +1923,28 @@ async function run() { | |||
| 1925 | 1923 | run().catch(console.error); | |
| 1926 | 1924 | ``` | |
| 1927 | 1925 | ||
| 1926 | + Remember to handle the `signal` argument passed into the async generator. | ||
| 1927 | + Especially in the case where the async generator is the source for the | ||
| 1928 | + pipeline (i.e. first argument) or the pipeline will never complete. | ||
| 1929 | + | ||
| 1930 | + ```js | ||
| 1931 | + const { pipeline } = require('stream/promises'); | ||
| 1932 | + const fs = require('fs'); | ||
| 1933 | + | ||
| 1934 | + async function run() { | ||
| 1935 | + await pipeline( | ||
| 1936 | + async function * (signal) { | ||
| 1937 | + await someLongRunningfn({ signal }); | ||
| 1938 | + yield 'asd'; | ||
| 1939 | + }, | ||
| 1940 | + fs.createWriteStream('uppercase.txt') | ||
| 1941 | + ); | ||
| 1942 | + console.log('Pipeline succeeded.'); | ||
| 1943 | + } | ||
| 1944 | + | ||
| 1945 | + run().catch(console.error); | ||
| 1946 | + ``` | ||
| 1947 | + | ||
| 1928 | 1948 | `stream.pipeline()` will call `stream.destroy(err)` on all streams except: | |
| 1929 | 1949 | * `Readable` streams which have emitted `'end'` or `'close'`. | |
| 1930 | 1950 | * `Writable` streams which have emitted `'finish'` or `'close'`. | |
@@ -3342,13 +3362,20 @@ the `Readable.from()` utility method: | |||
| 3342 | 3362 | ```js | |
| 3343 | 3363 | const { Readable } = require('stream'); | |
| 3344 | 3364 | ||
| 3365 | + const ac = new AbortController(); | ||
| 3366 | + const signal = ac.signal; | ||
| 3367 | + | ||
| 3345 | 3368 | async function * generate() { | |
| 3346 | 3369 | yield 'a'; | |
| 3370 | + await someLongRunningFn({ signal }); | ||
| 3347 | 3371 | yield 'b'; | |
| 3348 | 3372 | yield 'c'; | |
| 3349 | 3373 | } | |
| 3350 | 3374 | ||
| 3351 | 3375 | const readable = Readable.from(generate()); | |
| 3376 | + readable.on('close', () => { | ||
| 3377 | + ac.abort(); | ||
| 3378 | + }); | ||
| 3352 | 3379 | ||
| 3353 | 3380 | readable.on('data', (chunk) => { | |
| 3354 | 3381 | console.log(chunk); | |
@@ -3368,21 +3395,31 @@ const { pipeline: pipelinePromise } = require('stream/promises'); | |||
| 3368 | 3395 | ||
| 3369 | 3396 | const writable = fs.createWriteStream('./file'); | |
| 3370 | 3397 | ||
| 3398 | + const ac = new AbortController(); | ||
| 3399 | + const signal = ac.signal; | ||
| 3400 | + | ||
| 3401 | + const iterator = createIterator({ signal }); | ||
| 3402 | + | ||
| 3371 | 3403 | // Callback Pattern | |
| 3372 | 3404 | pipeline(iterator, writable, (err, value) => { | |
| 3373 | 3405 | if (err) { | |
| 3374 | 3406 | console.error(err); | |
| 3375 | 3407 | } else { | |
| 3376 | 3408 | console.log(value, 'value returned'); | |
| 3377 | 3409 | } | |
| 3410 | + }).on('close', () => { | ||
| 3411 | + ac.abort(); | ||
| 3378 | 3412 | }); | |
| 3379 | 3413 | ||
| 3380 | 3414 | // Promise Pattern | |
| 3381 | 3415 | pipelinePromise(iterator, writable) | |
| 3382 | 3416 | .then((value) => { | |
| 3383 | 3417 | console.log(value, 'value returned'); | |
| 3384 | 3418 | }) | |
| 3385 | - .catch(console.error); | ||
| 3419 | + .catch((err) => { | ||
| 3420 | + console.error(err); | ||
| 3421 | + ac.abort(); | ||
| 3422 | + }); | ||
| 3386 | 3423 | ``` | |
| 3387 | 3424 | ||
| 3388 | 3425 | <!--type=misc--> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | - const pipeline = require('internal/streams/pipeline'); | ||
| 3 | + const { pipeline } = require('internal/streams/pipeline'); | ||
| 4 | 4 | const Duplex = require('internal/streams/duplex'); | |
| 5 | 5 | const { destroyer } = require('internal/streams/destroy'); | |
| 6 | 6 | const { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ const from = require('internal/streams/from'); | |||
| 26 | 26 | const { | |
| 27 | 27 | isBlob, | |
| 28 | 28 | } = require('internal/blob'); | |
| 29 | + const { AbortController } = require('internal/abort_controller'); | ||
| 29 | 30 | ||
| 30 | 31 | const { | |
| 31 | 32 | FunctionPrototypeCall | |
@@ -81,14 +82,15 @@ module.exports = function duplexify(body, name) { | |||
| 81 | 82 | // } | |
| 82 | 83 | ||
| 83 | 84 | if (typeof body === 'function') { | |
| 84 | - const { value, write, final } = fromAsyncGen(body); | ||
| 85 | + const { value, write, final, destroy } = fromAsyncGen(body); | ||
| 85 | 86 | ||
| 86 | 87 | if (isIterable(value)) { | |
| 87 | 88 | return from(Duplexify, value, { | |
| 88 | 89 | // TODO (ronag): highWaterMark? | |
| 89 | 90 | objectMode: true, | |
| 90 | 91 | write, | |
| 91 | - final | ||
| 92 | + final, | ||
| 93 | + destroy | ||
| 92 | 94 | }); | |
| 93 | 95 | } | |
| 94 | 96 | ||
@@ -123,7 +125,8 @@ module.exports = function duplexify(body, name) { | |||
| 123 | 125 | process.nextTick(cb, err); | |
| 124 | 126 | } | |
| 125 | 127 | }); | |
| 126 | - } | ||
| 128 | + }, | ||
| 129 | + destroy | ||
| 127 | 130 | }); | |
| 128 | 131 | } | |
| 129 | 132 | ||
@@ -202,15 +205,18 @@ module.exports = function duplexify(body, name) { | |||
| 202 | 205 | ||
| 203 | 206 | function fromAsyncGen(fn) { | |
| 204 | 207 | let { promise, resolve } = createDeferredPromise(); | |
| 208 | + const ac = new AbortController(); | ||
| 209 | + const signal = ac.signal; | ||
| 205 | 210 | const value = fn(async function*() { | |
| 206 | 211 | while (true) { | |
| 207 | 212 | const { chunk, done, cb } = await promise; | |
| 208 | 213 | process.nextTick(cb); | |
| 209 | 214 | if (done) return; | |
| 215 | + if (signal.aborted) throw new AbortError(); | ||
| 210 | 216 | yield chunk; | |
| 211 | 217 | ({ promise, resolve } = createDeferredPromise()); | |
| 212 | 218 | } | |
| 213 | - }()); | ||
| 219 | + }(), { signal }); | ||
| 214 | 220 | ||
| 215 | 221 | return { | |
| 216 | 222 | value, | |
@@ -219,6 +225,10 @@ function fromAsyncGen(fn) { | |||
| 219 | 225 | }, | |
| 220 | 226 | final(cb) { | |
| 221 | 227 | resolve({ done: true, cb }); | |
| 228 | + }, | ||
| 229 | + destroy(err, cb) { | ||
| 230 | + ac.abort(); | ||
| 231 | + cb(err); | ||
| 222 | 232 | } | |
| 223 | 233 | }; | |
| 224 | 234 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,15 +21,20 @@ const { | |||
| 21 | 21 | ERR_MISSING_ARGS, | |
| 22 | 22 | ERR_STREAM_DESTROYED, | |
| 23 | 23 | }, | |
| 24 | + AbortError, | ||
| 24 | 25 | } = require('internal/errors'); | |
| 25 | 26 | ||
| 26 | - const { validateCallback } = require('internal/validators'); | ||
| 27 | + const { | ||
| 28 | + validateCallback, | ||
| 29 | + validateAbortSignal | ||
| 30 | + } = require('internal/validators'); | ||
| 27 | 31 | ||
| 28 | 32 | const { | |
| 29 | 33 | isIterable, | |
| 30 | 34 | isReadableNodeStream, | |
| 31 | 35 | isNodeStream, | |
| 32 | 36 | } = require('internal/streams/utils'); | |
| 37 | + const { AbortController } = require('internal/abort_controller'); | ||
| 33 | 38 | ||
| 34 | 39 | let PassThrough; | |
| 35 | 40 | let Readable; | |
@@ -168,19 +173,37 @@ function pipeline(...streams) { | |||
| 168 | 173 | streams = streams[0]; | |
| 169 | 174 | } | |
| 170 | 175 | ||
| 176 | + return pipelineImpl(streams, callback); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + function pipelineImpl(streams, callback, opts) { | ||
| 171 | 180 | if (streams.length < 2) { | |
| 172 | 181 | throw new ERR_MISSING_ARGS('streams'); | |
| 173 | 182 | } | |
| 174 | 183 | ||
| 184 | + const ac = new AbortController(); | ||
| 185 | + const signal = ac.signal; | ||
| 186 | + const outerSignal = opts?.signal; | ||
| 187 | + | ||
| 188 | + validateAbortSignal(outerSignal, 'options.signal'); | ||
| 189 | + | ||
| 190 | + function abort() { | ||
| 191 | + finishImpl(new AbortError()); | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + outerSignal?.addEventListener('abort', abort); | ||
| 195 | + | ||
| 175 | 196 | let error; | |
| 176 | 197 | let value; | |
| 177 | 198 | const destroys = []; | |
| 178 | 199 | ||
| 179 | 200 | let finishCount = 0; | |
| 180 | 201 | ||
| 181 | 202 | function finish(err) { | |
| 182 | - const final = --finishCount === 0; | ||
| 203 | + finishImpl(err, --finishCount === 0); | ||
| 204 | + } | ||
| 183 | 205 | ||
| 206 | + function finishImpl(err, final) { | ||
| 184 | 207 | if (err && (!error || error.code === 'ERR_STREAM_PREMATURE_CLOSE')) { | |
| 185 | 208 | error = err; | |
| 186 | 209 | } | |
@@ -193,6 +216,9 @@ function pipeline(...streams) { | |||
| 193 | 216 | destroys.shift()(error); | |
| 194 | 217 | } | |
| 195 | 218 | ||
| 219 | + outerSignal?.removeEventListener('abort', abort); | ||
| 220 | + ac.abort(); | ||
| 221 | + | ||
| 196 | 222 | if (final) { | |
| 197 | 223 | callback(error, value); | |
| 198 | 224 | } | |
@@ -211,7 +237,7 @@ function pipeline(...streams) { | |||
| 211 | 237 | ||
| 212 | 238 | if (i === 0) { | |
| 213 | 239 | if (typeof stream === 'function') { | |
| 214 | - ret = stream(); | ||
| 240 | + ret = stream({ signal }); | ||
| 215 | 241 | if (!isIterable(ret)) { | |
| 216 | 242 | throw new ERR_INVALID_RETURN_VALUE( | |
| 217 | 243 | 'Iterable, AsyncIterable or Stream', 'source', ret); | |
@@ -223,7 +249,7 @@ function pipeline(...streams) { | |||
| 223 | 249 | } | |
| 224 | 250 | } else if (typeof stream === 'function') { | |
| 225 | 251 | ret = makeAsyncIterable(ret); | |
| 226 | - ret = stream(ret); | ||
| 252 | + ret = stream(ret, { signal }); | ||
| 227 | 253 | ||
| 228 | 254 | if (reading) { | |
| 229 | 255 | if (!isIterable(ret, true)) { | |
@@ -291,7 +317,11 @@ function pipeline(...streams) { | |||
| 291 | 317 | } | |
| 292 | 318 | } | |
| 293 | 319 | ||
| 320 | + if (signal?.aborted || outerSignal?.aborted) { | ||
| 321 | + process.nextTick(abort); | ||
| 322 | + } | ||
| 323 | + | ||
| 294 | 324 | return ret; | |
| 295 | 325 | } | |
| 296 | 326 | ||
| 297 | - module.exports = pipeline; | ||
| 327 | + module.exports = { pipelineImpl, pipeline }; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,8 +29,8 @@ const { | |||
| 29 | 29 | promisify: { custom: customPromisify }, | |
| 30 | 30 | } = require('internal/util'); | |
| 31 | 31 | ||
| 32 | - const pipeline = require('internal/streams/pipeline'); | ||
| 33 | 32 | const compose = require('internal/streams/compose'); | |
| 33 | + const { pipeline } = require('internal/streams/pipeline'); | ||
| 34 | 34 | const { destroyer } = require('internal/streams/destroy'); | |
| 35 | 35 | const eos = require('internal/streams/end-of-stream'); | |
| 36 | 36 | const internalBuffer = require('internal/buffer'); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,20 +5,12 @@ const { | |||
| 5 | 5 | Promise, | |
| 6 | 6 | } = primordials; | |
| 7 | 7 | ||
| 8 | - const { | ||
| 9 | - addAbortSignalNoValidate, | ||
| 10 | - } = require('internal/streams/add-abort-signal'); | ||
| 11 | - | ||
| 12 | - const { | ||
| 13 | - validateAbortSignal, | ||
| 14 | - } = require('internal/validators'); | ||
| 15 | - | ||
| 16 | 8 | const { | |
| 17 | 9 | isIterable, | |
| 18 | 10 | isNodeStream, | |
| 19 | 11 | } = require('internal/streams/utils'); | |
| 20 | 12 | ||
| 21 | - const pl = require('internal/streams/pipeline'); | ||
| 13 | + const { pipelineImpl: pl } = require('internal/streams/pipeline'); | ||
| 22 | 14 | const eos = require('internal/streams/end-of-stream'); | |
| 23 | 15 | ||
| 24 | 16 | function pipeline(...streams) { | |
@@ -29,19 +21,15 @@ function pipeline(...streams) { | |||
| 29 | 21 | !isNodeStream(lastArg) && !isIterable(lastArg)) { | |
| 30 | 22 | const options = ArrayPrototypePop(streams); | |
| 31 | 23 | signal = options.signal; | |
| 32 | - validateAbortSignal(signal, 'options.signal'); | ||
| 33 | 24 | } | |
| 34 | 25 | ||
| 35 | - const pipe = pl(...streams, (err, value) => { | ||
| 26 | + pl(streams, (err, value) => { | ||
| 36 | 27 | if (err) { | |
| 37 | 28 | reject(err); | |
| 38 | 29 | } else { | |
| 39 | 30 | resolve(value); | |
| 40 | 31 | } | |
| 41 | - }); | ||
| 42 | - if (signal) { | ||
| 43 | - addAbortSignalNoValidate(signal, pipe); | ||
| 44 | - } | ||
| 32 | + }, { signal }); | ||
| 45 | 33 | }); | |
| 46 | 34 | } | |
| 47 | 35 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,10 +11,12 @@ const { | |||
| 11 | 11 | Duplex, | |
| 12 | 12 | addAbortSignal, | |
| 13 | 13 | } = require('stream'); | |
| 14 | + const pipelinep = require('stream/promises').pipeline; | ||
| 14 | 15 | const assert = require('assert'); | |
| 15 | 16 | const http = require('http'); | |
| 16 | 17 | const { promisify } = require('util'); | |
| 17 | 18 | const net = require('net'); | |
| 19 | + const tsp = require('timers/promises'); | ||
| 18 | 20 | ||
| 19 | 21 | { | |
| 20 | 22 | let finished = false; | |
@@ -1387,3 +1389,20 @@ const net = require('net'); | |||
| 1387 | 1389 | assert.strictEqual(res, content); | |
| 1388 | 1390 | })); | |
| 1389 | 1391 | } | |
| 1392 | + | ||
| 1393 | + { | ||
| 1394 | + const ac = new AbortController(); | ||
| 1395 | + const signal = ac.signal; | ||
| 1396 | + pipelinep( | ||
| 1397 | + async function * ({ signal }) { | ||
| 1398 | + await tsp.setTimeout(1e6, signal); | ||
| 1399 | + }, | ||
| 1400 | + async function(source) { | ||
| 1401 | + | ||
| 1402 | + }, | ||
| 1403 | + { signal } | ||
| 1404 | + ).catch(common.mustCall((err) => { | ||
| 1405 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 1406 | + })); | ||
| 1407 | + ac.abort(); | ||
| 1408 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments