| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 32a5b8f commit cad9d20
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1234,6 +1234,9 @@ All the [caveats][] for `fs.watch()` also apply to `fsPromises.watch()`. | |||
| 1234 | 1234 | <!-- YAML | |
| 1235 | 1235 | added: v10.0.0 | |
| 1236 | 1236 | changes: | |
| 1237 | + - version: REPLACEME | ||
| 1238 | + pr-url: https://github.com/nodejs/node/pull/37490 | ||
| 1239 | + description: The `data` argument supports `AsyncIterable`, `Iterable` & `Stream`. | ||
| 1237 | 1240 | - version: v14.17.0 | |
| 1238 | 1241 | pr-url: https://github.com/nodejs/node/pull/35993 | |
| 1239 | 1242 | description: The options argument may include an AbortSignal to abort an | |
@@ -1249,7 +1252,8 @@ changes: | |||
| 1249 | 1252 | --> | |
| 1250 | 1253 | ||
| 1251 | 1254 | * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` | |
| 1252 | - * `data` {string|Buffer|Uint8Array|Object} | ||
| 1255 | + * `data` {string|Buffer|Uint8Array|Object|AsyncIterable|Iterable | ||
| 1256 | + |Stream} | ||
| 1253 | 1257 | * `options` {Object|string} | |
| 1254 | 1258 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 1255 | 1259 | * `mode` {integer} **Default:** `0o666` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,7 @@ const { | |||
| 25 | 25 | const binding = internalBinding('fs'); | |
| 26 | 26 | const { Buffer } = require('buffer'); | |
| 27 | 27 | ||
| 28 | - const { codes, hideStackFrames } = require('internal/errors'); | ||
| 28 | + const { AbortError, codes, hideStackFrames } = require('internal/errors'); | ||
| 29 | 29 | const { | |
| 30 | 30 | ERR_FS_FILE_TOO_LARGE, | |
| 31 | 31 | ERR_INVALID_ARG_TYPE, | |
@@ -70,6 +70,7 @@ const { | |||
| 70 | 70 | const pathModule = require('path'); | |
| 71 | 71 | const { promisify } = require('internal/util'); | |
| 72 | 72 | const { watch } = require('internal/fs/watchers'); | |
| 73 | + const { isIterable } = require('internal/streams/utils'); | ||
| 73 | 74 | ||
| 74 | 75 | const kHandle = Symbol('kHandle'); | |
| 75 | 76 | const kFd = Symbol('kFd'); | |
@@ -251,8 +252,23 @@ async function fsCall(fn, handle, ...args) { | |||
| 251 | 252 | } | |
| 252 | 253 | } | |
| 253 | 254 | ||
| 254 | - async function writeFileHandle(filehandle, data, signal) { | ||
| 255 | - // `data` could be any kind of typed array. | ||
| 255 | + function checkAborted(signal) { | ||
| 256 | + if (signal && signal.aborted) | ||
| 257 | + throw new AbortError(); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + async function writeFileHandle(filehandle, data, signal, encoding) { | ||
| 261 | + checkAborted(signal); | ||
| 262 | + if (isCustomIterable(data)) { | ||
| 263 | + for await (const buf of data) { | ||
| 264 | + checkAborted(signal); | ||
| 265 | + await write( | ||
| 266 | + filehandle, buf, undefined, | ||
| 267 | + isArrayBufferView(buf) ? buf.length : encoding); | ||
| 268 | + checkAborted(signal); | ||
| 269 | + } | ||
| 270 | + return; | ||
| 271 | + } | ||
| 256 | 272 | data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | |
| 257 | 273 | let remaining = data.length; | |
| 258 | 274 | if (remaining === 0) return; | |
@@ -422,7 +438,7 @@ async function readv(handle, buffers, position) { | |||
| 422 | 438 | } | |
| 423 | 439 | ||
| 424 | 440 | async function write(handle, buffer, offset, length, position) { | |
| 425 | - if (buffer.length === 0) | ||
| 441 | + if (buffer && buffer.length === 0) | ||
| 426 | 442 | return { bytesWritten: 0, buffer }; | |
| 427 | 443 | ||
| 428 | 444 | if (isArrayBufferView(buffer)) { | |
@@ -664,22 +680,26 @@ async function writeFile(path, data, options) { | |||
| 664 | 680 | options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); | |
| 665 | 681 | const flag = options.flag || 'w'; | |
| 666 | 682 | ||
| 667 | - if (!isArrayBufferView(data)) { | ||
| 683 | + if (!isArrayBufferView(data) && !isCustomIterable(data)) { | ||
| 668 | 684 | validateStringAfterArrayBufferView(data, 'data'); | |
| 669 | 685 | data = Buffer.from(data, options.encoding || 'utf8'); | |
| 670 | 686 | } | |
| 671 | 687 | ||
| 672 | 688 | validateAbortSignal(options.signal); | |
| 673 | 689 | if (path instanceof FileHandle) | |
| 674 | - return writeFileHandle(path, data, options.signal); | ||
| 690 | + return writeFileHandle(path, data, options.signal, options.encoding); | ||
| 675 | 691 | ||
| 676 | 692 | if (options.signal?.aborted) { | |
| 677 | 693 | throw lazyDOMException('The operation was aborted', 'AbortError'); | |
| 678 | 694 | } | |
| 679 | 695 | ||
| 680 | 696 | const fd = await open(path, flag, options.mode); | |
| 681 | - const { signal } = options; | ||
| 682 | - return PromisePrototypeFinally(writeFileHandle(fd, data, signal), fd.close); | ||
| 697 | + return PromisePrototypeFinally( | ||
| 698 | + writeFileHandle(fd, data, options.signal, options.encoding), fd.close); | ||
| 699 | + } | ||
| 700 | + | ||
| 701 | + function isCustomIterable(obj) { | ||
| 702 | + return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string'; | ||
| 683 | 703 | } | |
| 684 | 704 | ||
| 685 | 705 | 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 | |
|---|---|---|---|
@@ -8,20 +8,115 @@ const path = require('path'); | |||
| 8 | 8 | const tmpdir = require('../common/tmpdir'); | |
| 9 | 9 | const assert = require('assert'); | |
| 10 | 10 | const tmpDir = tmpdir.path; | |
| 11 | + const { Readable } = require('stream'); | ||
| 11 | 12 | ||
| 12 | 13 | tmpdir.refresh(); | |
| 13 | 14 | ||
| 14 | 15 | const dest = path.resolve(tmpDir, 'tmp.txt'); | |
| 15 | 16 | const otherDest = path.resolve(tmpDir, 'tmp-2.txt'); | |
| 16 | 17 | const buffer = Buffer.from('abc'.repeat(1000)); | |
| 17 | 18 | const buffer2 = Buffer.from('xyz'.repeat(1000)); | |
| 19 | + const stream = Readable.from(['a', 'b', 'c']); | ||
| 20 | + const stream2 = Readable.from(['ümlaut', ' ', 'sechzig']); | ||
| 21 | + const iterable = { | ||
| 22 | + expected: 'abc', | ||
| 23 | + *[Symbol.iterator]() { | ||
| 24 | + yield 'a'; | ||
| 25 | + yield 'b'; | ||
| 26 | + yield 'c'; | ||
| 27 | + } | ||
| 28 | + }; | ||
| 29 | + function iterableWith(value) { | ||
| 30 | + return { | ||
| 31 | + *[Symbol.iterator]() { | ||
| 32 | + yield value; | ||
| 33 | + } | ||
| 34 | + }; | ||
| 35 | + } | ||
| 36 | + const bufferIterable = { | ||
| 37 | + expected: 'abc', | ||
| 38 | + *[Symbol.iterator]() { | ||
| 39 | + yield Buffer.from('a'); | ||
| 40 | + yield Buffer.from('b'); | ||
| 41 | + yield Buffer.from('c'); | ||
| 42 | + } | ||
| 43 | + }; | ||
| 44 | + const asyncIterable = { | ||
| 45 | + expected: 'abc', | ||
| 46 | + async* [Symbol.asyncIterator]() { | ||
| 47 | + yield 'a'; | ||
| 48 | + yield 'b'; | ||
| 49 | + yield 'c'; | ||
| 50 | + } | ||
| 51 | + }; | ||
| 18 | 52 | ||
| 19 | 53 | async function doWrite() { | |
| 20 | 54 | await fsPromises.writeFile(dest, buffer); | |
| 21 | 55 | const data = fs.readFileSync(dest); | |
| 22 | 56 | assert.deepStrictEqual(data, buffer); | |
| 23 | 57 | } | |
| 24 | 58 | ||
| 59 | + async function doWriteStream() { | ||
| 60 | + await fsPromises.writeFile(dest, stream); | ||
| 61 | + const expected = 'abc'; | ||
| 62 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 63 | + assert.deepStrictEqual(data, expected); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + async function doWriteStreamWithCancel() { | ||
| 67 | + const controller = new AbortController(); | ||
| 68 | + const { signal } = controller; | ||
| 69 | + process.nextTick(() => controller.abort()); | ||
| 70 | + assert.rejects(fsPromises.writeFile(otherDest, stream, { signal }), { | ||
| 71 | + name: 'AbortError' | ||
| 72 | + }); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + async function doWriteIterable() { | ||
| 76 | + await fsPromises.writeFile(dest, iterable); | ||
| 77 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 78 | + assert.deepStrictEqual(data, iterable.expected); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + async function doWriteInvalidIterable() { | ||
| 82 | + await Promise.all( | ||
| 83 | + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => | ||
| 84 | + assert.rejects(fsPromises.writeFile(dest, iterableWith(value)), { | ||
| 85 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 86 | + }) | ||
| 87 | + ) | ||
| 88 | + ); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + async function doWriteIterableWithEncoding() { | ||
| 92 | + await fsPromises.writeFile(dest, stream2, 'latin1'); | ||
| 93 | + const expected = 'ümlaut sechzig'; | ||
| 94 | + const data = fs.readFileSync(dest, 'latin1'); | ||
| 95 | + assert.deepStrictEqual(data, expected); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + async function doWriteBufferIterable() { | ||
| 99 | + await fsPromises.writeFile(dest, bufferIterable); | ||
| 100 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 101 | + assert.deepStrictEqual(data, bufferIterable.expected); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + async function doWriteAsyncIterable() { | ||
| 105 | + await fsPromises.writeFile(dest, asyncIterable); | ||
| 106 | + const data = fs.readFileSync(dest, 'utf-8'); | ||
| 107 | + assert.deepStrictEqual(data, asyncIterable.expected); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + async function doWriteInvalidValues() { | ||
| 111 | + await Promise.all( | ||
| 112 | + [42, 42n, {}, Symbol('42'), true, undefined, null, NaN].map((value) => | ||
| 113 | + assert.rejects(fsPromises.writeFile(dest, value), { | ||
| 114 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 115 | + }) | ||
| 116 | + ) | ||
| 117 | + ); | ||
| 118 | + } | ||
| 119 | + | ||
| 25 | 120 | async function doWriteWithCancel() { | |
| 26 | 121 | const controller = new AbortController(); | |
| 27 | 122 | const { signal } = controller; | |
@@ -51,9 +146,18 @@ async function doReadWithEncoding() { | |||
| 51 | 146 | assert.deepStrictEqual(data, syncData); | |
| 52 | 147 | } | |
| 53 | 148 | ||
| 54 | - doWrite() | ||
| 55 | - .then(doWriteWithCancel) | ||
| 56 | - .then(doAppend) | ||
| 57 | - .then(doRead) | ||
| 58 | - .then(doReadWithEncoding) | ||
| 59 | - .then(common.mustCall()); | ||
| 149 | + (async () => { | ||
| 150 | + await doWrite(); | ||
| 151 | + await doWriteWithCancel(); | ||
| 152 | + await doAppend(); | ||
| 153 | + await doRead(); | ||
| 154 | + await doReadWithEncoding(); | ||
| 155 | + await doWriteStream(); | ||
| 156 | + await doWriteStreamWithCancel(); | ||
| 157 | + await doWriteIterable(); | ||
| 158 | + await doWriteInvalidIterable(); | ||
| 159 | + await doWriteIterableWithEncoding(); | ||
| 160 | + await doWriteBufferIterable(); | ||
| 161 | + await doWriteAsyncIterable(); | ||
| 162 | + await doWriteInvalidValues(); | ||
| 163 | + })().then(common.mustCall()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments