| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4385,6 +4385,10 @@ details. | |||
| 4385 | 4385 | <!-- YAML | |
| 4386 | 4386 | added: v0.1.29 | |
| 4387 | 4387 | changes: | |
| 4388 | + - version: REPLACEME | ||
| 4389 | + pr-url: https://github.com/nodejs/node/pull/35993 | ||
| 4390 | + description: The options argument may include an AbortSignal to abort an | ||
| 4391 | + ongoing writeFile request. | ||
| 4388 | 4392 | - version: v14.12.0 | |
| 4389 | 4393 | pr-url: https://github.com/nodejs/node/pull/34993 | |
| 4390 | 4394 | description: The `data` parameter will stringify an object with an | |
@@ -4419,6 +4423,7 @@ changes: | |||
| 4419 | 4423 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 4420 | 4424 | * `mode` {integer} **Default:** `0o666` | |
| 4421 | 4425 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`. | |
| 4426 | + * `signal` {AbortSignal} allows aborting an in-progress writeFile | ||
| 4422 | 4427 | * `callback` {Function} | |
| 4423 | 4428 | * `err` {Error} | |
| 4424 | 4429 | ||
@@ -4450,6 +4455,28 @@ It is unsafe to use `fs.writeFile()` multiple times on the same file without | |||
| 4450 | 4455 | waiting for the callback. For this scenario, [`fs.createWriteStream()`][] is | |
| 4451 | 4456 | recommended. | |
| 4452 | 4457 | ||
| 4458 | + Similarly to `fs.readFile` - `fs.writeFile` is a convenience method that | ||
| 4459 | + performs multiple `write` calls internally to write the buffer passed to it. | ||
| 4460 | + For performance sensitive code consider using [`fs.createWriteStream()`][]. | ||
| 4461 | + | ||
| 4462 | + It is possible to use an {AbortSignal} to cancel an `fs.writeFile()`. | ||
| 4463 | + Cancelation is "best effort", and some amount of data is likely still | ||
| 4464 | + to be written. | ||
| 4465 | + | ||
| 4466 | + ```js | ||
| 4467 | + const controller = new AbortController(); | ||
| 4468 | + const { signal } = controller; | ||
| 4469 | + const data = new Uint8Array(Buffer.from('Hello Node.js')); | ||
| 4470 | + fs.writeFile('message.txt', data, { signal }, (err) => { | ||
| 4471 | + // When a request is aborted - the callback is called with an AbortError | ||
| 4472 | + }); | ||
| 4473 | + // When the request should be aborted | ||
| 4474 | + controller.abort(); | ||
| 4475 | + ``` | ||
| 4476 | + | ||
| 4477 | + Aborting an ongoing request does not abort individual operating | ||
| 4478 | + system requests but rather the internal buffering `fs.writeFile` performs. | ||
| 4479 | + | ||
| 4453 | 4480 | ### Using `fs.writeFile()` with file descriptors | |
| 4454 | 4481 | ||
| 4455 | 4482 | When `file` is a file descriptor, the behavior is almost identical to directly | |
@@ -5717,6 +5744,10 @@ The `atime` and `mtime` arguments follow these rules: | |||
| 5717 | 5744 | <!-- YAML | |
| 5718 | 5745 | added: v10.0.0 | |
| 5719 | 5746 | changes: | |
| 5747 | + - version: REPLACEME | ||
| 5748 | + pr-url: https://github.com/nodejs/node/pull/35993 | ||
| 5749 | + description: The options argument may include an AbortSignal to abort an | ||
| 5750 | + ongoing writeFile request. | ||
| 5720 | 5751 | - version: v14.12.0 | |
| 5721 | 5752 | pr-url: https://github.com/nodejs/node/pull/34993 | |
| 5722 | 5753 | description: The `data` parameter will stringify an object with an | |
@@ -5733,6 +5764,7 @@ changes: | |||
| 5733 | 5764 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 5734 | 5765 | * `mode` {integer} **Default:** `0o666` | |
| 5735 | 5766 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`. | |
| 5767 | + * `signal` {AbortSignal} allows aborting an in-progress writeFile | ||
| 5736 | 5768 | * Returns: {Promise} | |
| 5737 | 5769 | ||
| 5738 | 5770 | Asynchronously writes data to a file, replacing the file if it already exists. | |
@@ -5746,7 +5778,34 @@ If `options` is a string, then it specifies the encoding. | |||
| 5746 | 5778 | Any specified `FileHandle` has to support writing. | |
| 5747 | 5779 | ||
| 5748 | 5780 | It is unsafe to use `fsPromises.writeFile()` multiple times on the same file | |
| 5749 | - without waiting for the `Promise` to be resolved (or rejected). | ||
| 5781 | + without waiting for the `Promise` to be fulfilled (or rejected). | ||
| 5782 | + | ||
| 5783 | + Similarly to `fsPromises.readFile` - `fsPromises.writeFile` is a convenience | ||
| 5784 | + method that performs multiple `write` calls internally to write the buffer | ||
| 5785 | + passed to it. For performance sensitive code consider using | ||
| 5786 | + [`fs.createWriteStream()`][]. | ||
| 5787 | + | ||
| 5788 | + It is possible to use an {AbortSignal} to cancel an `fsPromises.writeFile()`. | ||
| 5789 | + Cancelation is "best effort", and some amount of data is likely still | ||
| 5790 | + to be written. | ||
| 5791 | + | ||
| 5792 | + ```js | ||
| 5793 | + const controller = new AbortController(); | ||
| 5794 | + const { signal } = controller; | ||
| 5795 | + const data = new Uint8Array(Buffer.from('Hello Node.js')); | ||
| 5796 | + (async () => { | ||
| 5797 | + try { | ||
| 5798 | + await fs.writeFile('message.txt', data, { signal }); | ||
| 5799 | + } catch (err) { | ||
| 5800 | + // When a request is aborted - err is an AbortError | ||
| 5801 | + } | ||
| 5802 | + })(); | ||
| 5803 | + // When the request should be aborted | ||
| 5804 | + controller.abort(); | ||
| 5805 | + ``` | ||
| 5806 | + | ||
| 5807 | + Aborting an ongoing request does not abort individual operating | ||
| 5808 | + system requests but rather the internal buffering `fs.writeFile` performs. | ||
| 5750 | 5809 | ||
| 5751 | 5810 | ## FS constants | |
| 5752 | 5811 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -71,6 +71,7 @@ const { | |||
| 71 | 71 | ERR_INVALID_CALLBACK, | |
| 72 | 72 | ERR_FEATURE_UNAVAILABLE_ON_PLATFORM | |
| 73 | 73 | }, | |
| 74 | + hideStackFrames, | ||
| 74 | 75 | uvException | |
| 75 | 76 | } = require('internal/errors'); | |
| 76 | 77 | ||
@@ -133,6 +134,13 @@ let ReadStream; | |||
| 133 | 134 | let WriteStream; | |
| 134 | 135 | let rimraf; | |
| 135 | 136 | let rimrafSync; | |
| 137 | + let DOMException; | ||
| 138 | + | ||
| 139 | + const lazyDOMException = hideStackFrames((message, name) => { | ||
| 140 | + if (DOMException === undefined) | ||
| 141 | + DOMException = internalBinding('messaging').DOMException; | ||
| 142 | + return new DOMException(message, name); | ||
| 143 | + }); | ||
| 136 | 144 | ||
| 137 | 145 | // These have to be separate because of how graceful-fs happens to do it's | |
| 138 | 146 | // monkeypatching. | |
@@ -1409,7 +1417,11 @@ function lutimesSync(path, atime, mtime) { | |||
| 1409 | 1417 | handleErrorFromBinding(ctx); | |
| 1410 | 1418 | } | |
| 1411 | 1419 | ||
| 1412 | - function writeAll(fd, isUserFd, buffer, offset, length, callback) { | ||
| 1420 | + function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) { | ||
| 1421 | + if (signal?.aborted) { | ||
| 1422 | + callback(lazyDOMException('The operation was aborted', 'AbortError')); | ||
| 1423 | + return; | ||
| 1424 | + } | ||
| 1413 | 1425 | // write(fd, buffer, offset, length, position, callback) | |
| 1414 | 1426 | fs.write(fd, buffer, offset, length, null, (writeErr, written) => { | |
| 1415 | 1427 | if (writeErr) { | |
@@ -1429,7 +1441,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, callback) { | |||
| 1429 | 1441 | } else { | |
| 1430 | 1442 | offset += written; | |
| 1431 | 1443 | length -= written; | |
| 1432 | - writeAll(fd, isUserFd, buffer, offset, length, callback); | ||
| 1444 | + writeAll(fd, isUserFd, buffer, offset, length, signal, callback); | ||
| 1433 | 1445 | } | |
| 1434 | 1446 | }); | |
| 1435 | 1447 | } | |
@@ -1446,16 +1458,22 @@ function writeFile(path, data, options, callback) { | |||
| 1446 | 1458 | ||
| 1447 | 1459 | if (isFd(path)) { | |
| 1448 | 1460 | const isUserFd = true; | |
| 1449 | - writeAll(path, isUserFd, data, 0, data.byteLength, callback); | ||
| 1461 | + const signal = options.signal; | ||
| 1462 | + writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback); | ||
| 1450 | 1463 | return; | |
| 1451 | 1464 | } | |
| 1452 | 1465 | ||
| 1466 | + if (options.signal?.aborted) { | ||
| 1467 | + callback(lazyDOMException('The operation was aborted', 'AbortError')); | ||
| 1468 | + return; | ||
| 1469 | + } | ||
| 1453 | 1470 | fs.open(path, flag, options.mode, (openErr, fd) => { | |
| 1454 | 1471 | if (openErr) { | |
| 1455 | 1472 | callback(openErr); | |
| 1456 | 1473 | } else { | |
| 1457 | 1474 | const isUserFd = false; | |
| 1458 | - writeAll(fd, isUserFd, data, 0, data.byteLength, callback); | ||
| 1475 | + const signal = options.signal; | ||
| 1476 | + writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback); | ||
| 1459 | 1477 | } | |
| 1460 | 1478 | }); | |
| 1461 | 1479 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -250,12 +250,15 @@ async function fsCall(fn, handle, ...args) { | |||
| 250 | 250 | } | |
| 251 | 251 | } | |
| 252 | 252 | ||
| 253 | - async function writeFileHandle(filehandle, data) { | ||
| 253 | + async function writeFileHandle(filehandle, data, signal) { | ||
| 254 | 254 | // `data` could be any kind of typed array. | |
| 255 | 255 | data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | |
| 256 | 256 | let remaining = data.length; | |
| 257 | 257 | if (remaining === 0) return; | |
| 258 | 258 | do { | |
| 259 | + if (signal?.aborted) { | ||
| 260 | + throw new lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 261 | + } | ||
| 259 | 262 | const { bytesWritten } = | |
| 260 | 263 | await write(filehandle, data, 0, | |
| 261 | 264 | MathMin(kWriteFileMaxChunkSize, data.length)); | |
@@ -633,9 +636,12 @@ async function writeFile(path, data, options) { | |||
| 633 | 636 | } | |
| 634 | 637 | ||
| 635 | 638 | if (path instanceof FileHandle) | |
| 636 | - return writeFileHandle(path, data); | ||
| 639 | + return writeFileHandle(path, data, options.signal); | ||
| 637 | 640 | ||
| 638 | 641 | const fd = await open(path, flag, options.mode); | |
| 642 | + if (options.signal?.aborted) { | ||
| 643 | + throw new lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 644 | + } | ||
| 639 | 645 | return PromisePrototypeFinally(writeFileHandle(fd, data), fd.close); | |
| 640 | 646 | } | |
| 641 | 647 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,6 +11,7 @@ const tmpDir = tmpdir.path; | |||
| 11 | 11 | tmpdir.refresh(); | |
| 12 | 12 | ||
| 13 | 13 | const dest = path.resolve(tmpDir, 'tmp.txt'); | |
| 14 | + const otherDest = path.resolve(tmpDir, 'tmp-2.txt'); | ||
| 14 | 15 | const buffer = Buffer.from('abc'.repeat(1000)); | |
| 15 | 16 | const buffer2 = Buffer.from('xyz'.repeat(1000)); | |
| 16 | 17 | ||
@@ -20,6 +21,15 @@ async function doWrite() { | |||
| 20 | 21 | assert.deepStrictEqual(data, buffer); | |
| 21 | 22 | } | |
| 22 | 23 | ||
| 24 | + async function doWriteWithCancel() { | ||
| 25 | + const controller = new AbortController(); | ||
| 26 | + const { signal } = controller; | ||
| 27 | + process.nextTick(() => controller.abort()); | ||
| 28 | + assert.rejects(fsPromises.writeFile(otherDest, buffer, { signal }), { | ||
| 29 | + name: 'AbortError' | ||
| 30 | + }); | ||
| 31 | + } | ||
| 32 | + | ||
| 23 | 33 | async function doAppend() { | |
| 24 | 34 | await fsPromises.appendFile(dest, buffer2); | |
| 25 | 35 | const data = fs.readFileSync(dest); | |
@@ -41,6 +51,7 @@ async function doReadWithEncoding() { | |||
| 41 | 51 | } | |
| 42 | 52 | ||
| 43 | 53 | doWrite() | |
| 54 | + .then(doWriteWithCancel) | ||
| 44 | 55 | .then(doAppend) | |
| 45 | 56 | .then(doRead) | |
| 46 | 57 | .then(doReadWithEncoding) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,3 +66,32 @@ fs.open(filename4, 'w+', common.mustSucceed((fd) => { | |||
| 66 | 66 | })); | |
| 67 | 67 | })); | |
| 68 | 68 | })); | |
| 69 | + | ||
| 70 | + | ||
| 71 | + { | ||
| 72 | + // Test that writeFile is cancellable with an AbortSignal. | ||
| 73 | + // Before the operation has started | ||
| 74 | + const controller = new AbortController(); | ||
| 75 | + const signal = controller.signal; | ||
| 76 | + const filename3 = join(tmpdir.path, 'test3.txt'); | ||
| 77 | + | ||
| 78 | + fs.writeFile(filename3, s, { signal }, common.mustCall((err) => { | ||
| 79 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 80 | + })); | ||
| 81 | + | ||
| 82 | + controller.abort(); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + { | ||
| 86 | + // Test that writeFile is cancellable with an AbortSignal. | ||
| 87 | + // After the operation has started | ||
| 88 | + const controller = new AbortController(); | ||
| 89 | + const signal = controller.signal; | ||
| 90 | + const filename4 = join(tmpdir.path, 'test4.txt'); | ||
| 91 | + | ||
| 92 | + fs.writeFile(filename4, s, { signal }, common.mustCall((err) => { | ||
| 93 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 94 | + })); | ||
| 95 | + | ||
| 96 | + process.nextTick(() => controller.abort()); | ||
| 97 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments