| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1241,6 +1241,9 @@ All the [caveats][] for `fs.watch()` also apply to `fsPromises.watch()`. | |||
| 1241 | 1241 | <!-- YAML | |
| 1242 | 1242 | added: v10.0.0 | |
| 1243 | 1243 | changes: | |
| 1244 | + - version: REPLACEME | ||
| 1245 | + pr-url: https://github.com/nodejs/node/pull/37490 | ||
| 1246 | + description: The `data` argument supports `AsyncIterable`, `Iterable` & `Stream`. | ||
| 1244 | 1247 | - version: v15.2.0 | |
| 1245 | 1248 | pr-url: https://github.com/nodejs/node/pull/35993 | |
| 1246 | 1249 | description: The options argument may include an AbortSignal to abort an | |
@@ -1256,7 +1259,8 @@ changes: | |||
| 1256 | 1259 | --> | |
| 1257 | 1260 | ||
| 1258 | 1261 | * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` | |
| 1259 | - * `data` {string|Buffer|Uint8Array|Object} | ||
| 1262 | + * `data` {string|Buffer|Uint8Array|Object|AsyncIterable|Iterable | ||
| 1263 | + |Stream} | ||
| 1260 | 1264 | * `options` {Object|string} | |
| 1261 | 1265 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 1262 | 1266 | * `mode` {integer} **Default:** `0o666` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,6 +79,7 @@ const pathModule = require('path'); | |||
| 79 | 79 | const { promisify } = require('internal/util'); | |
| 80 | 80 | const { EventEmitterMixin } = require('internal/event_target'); | |
| 81 | 81 | const { watch } = require('internal/fs/watchers'); | |
| 82 | + const { isIterable } = require('internal/streams/utils'); | ||
| 82 | 83 | ||
| 83 | 84 | const kHandle = Symbol('kHandle'); | |
| 84 | 85 | const kFd = Symbol('kFd'); | |
@@ -274,8 +275,18 @@ function checkAborted(signal) { | |||
| 274 | 275 | throw new AbortError(); | |
| 275 | 276 | } | |
| 276 | 277 | ||
| 277 | - async function writeFileHandle(filehandle, data, signal) { | ||
| 278 | - // `data` could be any kind of typed array. | ||
| 278 | + async function writeFileHandle(filehandle, data, signal, encoding) { | ||
| 279 | + checkAborted(signal); | ||
| 280 | + if (isCustomIterable(data)) { | ||
| 281 | + for await (const buf of data) { | ||
| 282 | + checkAborted(signal); | ||
| 283 | + await write( | ||
| 284 | + filehandle, buf, undefined, | ||
| 285 | + isArrayBufferView(buf) ? buf.length : encoding); | ||
| 286 | + checkAborted(signal); | ||
| 287 | + } | ||
| 288 | + return; | ||
| 289 | + } | ||
| 279 | 290 | data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | |
| 280 | 291 | let remaining = data.length; | |
| 281 | 292 | if (remaining === 0) return; | |
@@ -438,7 +449,7 @@ async function readv(handle, buffers, position) { | |||
| 438 | 449 | } | |
| 439 | 450 | ||
| 440 | 451 | async function write(handle, buffer, offset, length, position) { | |
| 441 | - if (buffer.length === 0) | ||
| 452 | + if (buffer?.length === 0) | ||
| 442 | 453 | return { bytesWritten: 0, buffer }; | |
| 443 | 454 | ||
| 444 | 455 | if (isArrayBufferView(buffer)) { | |
@@ -679,20 +690,24 @@ async function writeFile(path, data, options) { | |||
| 679 | 690 | options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); | |
| 680 | 691 | const flag = options.flag || 'w'; | |
| 681 | 692 | ||
| 682 | - if (!isArrayBufferView(data)) { | ||
| 693 | + if (!isArrayBufferView(data) && !isCustomIterable(data)) { | ||
| 683 | 694 | validateStringAfterArrayBufferView(data, 'data'); | |
| 684 | 695 | data = Buffer.from(data, options.encoding || 'utf8'); | |
| 685 | 696 | } | |
| 686 | 697 | ||
| 687 | 698 | validateAbortSignal(options.signal); | |
| 688 | 699 | if (path instanceof FileHandle) | |
| 689 | - return writeFileHandle(path, data, options.signal); | ||
| 700 | + return writeFileHandle(path, data, options.signal, options.encoding); | ||
| 690 | 701 | ||
| 691 | 702 | checkAborted(options.signal); | |
| 692 | 703 | ||
| 693 | 704 | const fd = await open(path, flag, options.mode); | |
| 694 | - const { signal } = options; | ||
| 695 | - return PromisePrototypeFinally(writeFileHandle(fd, data, signal), fd.close); | ||
| 705 | + return PromisePrototypeFinally( | ||
| 706 | + writeFileHandle(fd, data, options.signal, options.encoding), fd.close); | ||
| 707 | + } | ||
| 708 | + | ||
| 709 | + function isCustomIterable(obj) { | ||
| 710 | + return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string'; | ||
| 696 | 711 | } | |
| 697 | 712 | ||
| 698 | 713 | async function appendFile(path, data, options) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -121,7 +121,7 @@ const throwNextTick = (e) => { process.nextTick(() => { throw e; }); }; | |||
| 121 | 121 | } | |
| 122 | 122 | ||
| 123 | 123 | // Test that appendFile does not accept invalid data type (callback API). | |
| 124 | - [false, 5, {}, [], null, undefined].forEach(async (data) => { | ||
| 124 | + [false, 5, {}, null, undefined].forEach(async (data) => { | ||
| 125 | 125 | const errObj = { | |
| 126 | 126 | code: 'ERR_INVALID_ARG_TYPE', | |
| 127 | 127 | message: /"data"|"buffer"/ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,20 +7,115 @@ const path = require('path'); | |||
| 7 | 7 | const tmpdir = require('../common/tmpdir'); | |
| 8 | 8 | const assert = require('assert'); | |
| 9 | 9 | const tmpDir = tmpdir.path; | |
| 10 | + const { Readable } = require('stream'); | ||
| 10 | 11 | ||
| 11 | 12 | tmpdir.refresh(); | |
| 12 | 13 | ||
| 13 | 14 | const dest = path.resolve(tmpDir, 'tmp.txt'); | |
| 14 | 15 | const otherDest = path.resolve(tmpDir, 'tmp-2.txt'); | |
| 15 | 16 | const buffer = Buffer.from('abc'.repeat(1000)); | |
| 16 | 17 | const buffer2 = Buffer.from('xyz'.repeat(1000)); | |
| 18 | + const stream = Readable.from(['a', 'b', 'c']); | ||
| 19 | + const stream2 = Readable.from(['ümlaut', ' ', 'sechzig']); | ||
| 20 | + const iterable = { | ||
| 21 | + expected: 'abc', | ||
| 22 | + *[Symbol.iterator]() { | ||
| 23 | + yield 'a'; | ||
| 24 | + yield 'b'; | ||
| 25 | + yield 'c'; | ||
| 26 | + } | ||
| 27 | + }; | ||
| 28 | + function iterableWith(value) { | ||
| 29 | + return { | ||
| 30 | + *[Symbol.iterator]() { | ||
| 31 | + yield value; | ||
| 32 | + } | ||
| 33 | + }; | ||
| 34 | + } | ||
| 35 | + const bufferIterable = { | ||
| 36 | + expected: 'abc', | ||
| 37 | + *[Symbol.iterator]() { | ||
| 38 | + yield Buffer.from('a'); | ||
| 39 | + yield Buffer.from('b'); | ||
| 40 | + yield Buffer.from('c'); | ||
| 41 | + } | ||
| 42 | + }; | ||
| 43 | + const asyncIterable = { | ||
| 44 | + expected: 'abc', | ||
| 45 | + async* [Symbol.asyncIterator]() { | ||
| 46 | + yield 'a'; | ||
| 47 | + yield 'b'; | ||
| 48 | + yield 'c'; | ||
| 49 | + } | ||
| 50 | + }; | ||
| 17 | 51 | ||
| 18 | 52 | async function doWrite() { | |
| 19 | 53 | await fsPromises.writeFile(dest, buffer); | |
| 20 | 54 | const data = fs.readFileSync(dest); | |
| 21 | 55 | assert.deepStrictEqual(data, buffer); | |
| 22 | 56 | } | |
| 23 | 57 | ||
| 58 | + async function doWriteStream() { | ||
| 59 | + await fsPromises.writeFile(dest, stream); | ||
| 60 | + const expected = 'abc'; | ||
| 61 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 62 | + assert.deepStrictEqual(data, expected); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + async function doWriteStreamWithCancel() { | ||
| 66 | + const controller = new AbortController(); | ||
| 67 | + const { signal } = controller; | ||
| 68 | + process.nextTick(() => controller.abort()); | ||
| 69 | + assert.rejects(fsPromises.writeFile(otherDest, stream, { signal }), { | ||
| 70 | + name: 'AbortError' | ||
| 71 | + }); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + async function doWriteIterable() { | ||
| 75 | + await fsPromises.writeFile(dest, iterable); | ||
| 76 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 77 | + assert.deepStrictEqual(data, iterable.expected); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + async function doWriteInvalidIterable() { | ||
| 81 | + await Promise.all( | ||
| 82 | + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => | ||
| 83 | + assert.rejects(fsPromises.writeFile(dest, iterableWith(value)), { | ||
| 84 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 85 | + }) | ||
| 86 | + ) | ||
| 87 | + ); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + async function doWriteIterableWithEncoding() { | ||
| 91 | + await fsPromises.writeFile(dest, stream2, 'latin1'); | ||
| 92 | + const expected = 'ümlaut sechzig'; | ||
| 93 | + const data = fs.readFileSync(dest, 'latin1'); | ||
| 94 | + assert.deepStrictEqual(data, expected); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + async function doWriteBufferIterable() { | ||
| 98 | + await fsPromises.writeFile(dest, bufferIterable); | ||
| 99 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 100 | + assert.deepStrictEqual(data, bufferIterable.expected); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + async function doWriteAsyncIterable() { | ||
| 104 | + await fsPromises.writeFile(dest, asyncIterable); | ||
| 105 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 106 | + assert.deepStrictEqual(data, asyncIterable.expected); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + async function doWriteInvalidValues() { | ||
| 110 | + await Promise.all( | ||
| 111 | + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => | ||
| 112 | + assert.rejects(fsPromises.writeFile(dest, value), { | ||
| 113 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 114 | + }) | ||
| 115 | + ) | ||
| 116 | + ); | ||
| 117 | + } | ||
| 118 | + | ||
| 24 | 119 | async function doWriteWithCancel() { | |
| 25 | 120 | const controller = new AbortController(); | |
| 26 | 121 | const { signal } = controller; | |
@@ -50,9 +145,18 @@ async function doReadWithEncoding() { | |||
| 50 | 145 | assert.deepStrictEqual(data, syncData); | |
| 51 | 146 | } | |
| 52 | 147 | ||
| 53 | - doWrite() | ||
| 54 | - .then(doWriteWithCancel) | ||
| 55 | - .then(doAppend) | ||
| 56 | - .then(doRead) | ||
| 57 | - .then(doReadWithEncoding) | ||
| 58 | - .then(common.mustCall()); | ||
| 148 | + (async () => { | ||
| 149 | + await doWrite(); | ||
| 150 | + await doWriteWithCancel(); | ||
| 151 | + await doAppend(); | ||
| 152 | + await doRead(); | ||
| 153 | + await doReadWithEncoding(); | ||
| 154 | + await doWriteStream(); | ||
| 155 | + await doWriteStreamWithCancel(); | ||
| 156 | + await doWriteIterable(); | ||
| 157 | + await doWriteInvalidIterable(); | ||
| 158 | + await doWriteIterableWithEncoding(); | ||
| 159 | + await doWriteBufferIterable(); | ||
| 160 | + await doWriteAsyncIterable(); | ||
| 161 | + await doWriteInvalidValues(); | ||
| 162 | + })().then(common.mustCall()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments