| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4375,6 +4375,10 @@ details. | |||
| 4375 | 4375 | <!-- YAML | |
| 4376 | 4376 | added: v0.1.29 | |
| 4377 | 4377 | changes: | |
| 4378 | + - version: REPLACEME | ||
| 4379 | + pr-url: https://github.com/nodejs/node/pull/35993 | ||
| 4380 | + description: The options argument may include an AbortSignal to abort an | ||
| 4381 | + ongoing writeFile request. | ||
| 4378 | 4382 | - version: v14.12.0 | |
| 4379 | 4383 | pr-url: https://github.com/nodejs/node/pull/34993 | |
| 4380 | 4384 | description: The `data` parameter will stringify an object with an | |
@@ -4409,6 +4413,7 @@ changes: | |||
| 4409 | 4413 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 4410 | 4414 | * `mode` {integer} **Default:** `0o666` | |
| 4411 | 4415 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`. | |
| 4416 | + * `signal` {AbortSignal} allows aborting an in-progress writeFile | ||
| 4412 | 4417 | * `callback` {Function} | |
| 4413 | 4418 | * `err` {Error} | |
| 4414 | 4419 | ||
@@ -4440,6 +4445,28 @@ It is unsafe to use `fs.writeFile()` multiple times on the same file without | |||
| 4440 | 4445 | waiting for the callback. For this scenario, [`fs.createWriteStream()`][] is | |
| 4441 | 4446 | recommended. | |
| 4442 | 4447 | ||
| 4448 | + Similarly to `fs.readFile` - `fs.writeFile` is a convenience method that | ||
| 4449 | + performs multiple `write` calls internally to write the buffer passed to it. | ||
| 4450 | + For performance sensitive code consider using [`fs.createWriteStream()`][]. | ||
| 4451 | + | ||
| 4452 | + It is possible to use an {AbortSignal} to cancel an `fs.writeFile()`. | ||
| 4453 | + Cancelation is "best effort", and some amount of data is likely still | ||
| 4454 | + to be written. | ||
| 4455 | + | ||
| 4456 | + ```js | ||
| 4457 | + const controller = new AbortController(); | ||
| 4458 | + const { signal } = controller; | ||
| 4459 | + const data = new Uint8Array(Buffer.from('Hello Node.js')); | ||
| 4460 | + fs.writeFile('message.txt', data, { signal }, (err) => { | ||
| 4461 | + // When a request is aborted - the callback is called with an AbortError | ||
| 4462 | + }); | ||
| 4463 | + // When the request should be aborted | ||
| 4464 | + controller.abort(); | ||
| 4465 | + ``` | ||
| 4466 | + | ||
| 4467 | + Aborting an ongoing request does not abort individual operating | ||
| 4468 | + system requests but rather the internal buffering `fs.writeFile` performs. | ||
| 4469 | + | ||
| 4443 | 4470 | ### Using `fs.writeFile()` with file descriptors | |
| 4444 | 4471 | ||
| 4445 | 4472 | When `file` is a file descriptor, the behavior is almost identical to directly | |
@@ -5684,6 +5711,10 @@ The `atime` and `mtime` arguments follow these rules: | |||
| 5684 | 5711 | <!-- YAML | |
| 5685 | 5712 | added: v10.0.0 | |
| 5686 | 5713 | changes: | |
| 5714 | + - version: REPLACEME | ||
| 5715 | + pr-url: https://github.com/nodejs/node/pull/35993 | ||
| 5716 | + description: The options argument may include an AbortSignal to abort an | ||
| 5717 | + ongoing writeFile request. | ||
| 5687 | 5718 | - version: v14.12.0 | |
| 5688 | 5719 | pr-url: https://github.com/nodejs/node/pull/34993 | |
| 5689 | 5720 | description: The `data` parameter will stringify an object with an | |
@@ -5700,6 +5731,7 @@ changes: | |||
| 5700 | 5731 | * `encoding` {string|null} **Default:** `'utf8'` | |
| 5701 | 5732 | * `mode` {integer} **Default:** `0o666` | |
| 5702 | 5733 | * `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`. | |
| 5734 | + * `signal` {AbortSignal} allows aborting an in-progress writeFile | ||
| 5703 | 5735 | * Returns: {Promise} | |
| 5704 | 5736 | ||
| 5705 | 5737 | Asynchronously writes data to a file, replacing the file if it already exists. | |
@@ -5713,7 +5745,34 @@ If `options` is a string, then it specifies the encoding. | |||
| 5713 | 5745 | Any specified `FileHandle` has to support writing. | |
| 5714 | 5746 | ||
| 5715 | 5747 | It is unsafe to use `fsPromises.writeFile()` multiple times on the same file | |
| 5716 | - without waiting for the `Promise` to be resolved (or rejected). | ||
| 5748 | + without waiting for the `Promise` to be fulfilled (or rejected). | ||
| 5749 | + | ||
| 5750 | + Similarly to `fsPromises.readFile` - `fsPromises.writeFile` is a convenience | ||
| 5751 | + method that performs multiple `write` calls internally to write the buffer | ||
| 5752 | + passed to it. For performance sensitive code consider using | ||
| 5753 | + [`fs.createWriteStream()`][]. | ||
| 5754 | + | ||
| 5755 | + It is possible to use an {AbortSignal} to cancel an `fsPromises.writeFile()`. | ||
| 5756 | + Cancelation is "best effort", and some amount of data is likely still | ||
| 5757 | + to be written. | ||
| 5758 | + | ||
| 5759 | + ```js | ||
| 5760 | + const controller = new AbortController(); | ||
| 5761 | + const { signal } = controller; | ||
| 5762 | + const data = new Uint8Array(Buffer.from('Hello Node.js')); | ||
| 5763 | + (async () => { | ||
| 5764 | + try { | ||
| 5765 | + await fs.writeFile('message.txt', data, { signal }); | ||
| 5766 | + } catch (err) { | ||
| 5767 | + // When a request is aborted - err is an AbortError | ||
| 5768 | + } | ||
| 5769 | + })(); | ||
| 5770 | + // When the request should be aborted | ||
| 5771 | + controller.abort(); | ||
| 5772 | + ``` | ||
| 5773 | + | ||
| 5774 | + Aborting an ongoing request does not abort individual operating | ||
| 5775 | + system requests but rather the internal buffering `fs.writeFile` performs. | ||
| 5717 | 5776 | ||
| 5718 | 5777 | ## FS constants | |
| 5719 | 5778 | ||
| 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 | uvErrmapGet, | |
| 75 | 76 | uvException | |
| 76 | 77 | } = require('internal/errors'); | |
@@ -134,6 +135,13 @@ let ReadStream; | |||
| 134 | 135 | let WriteStream; | |
| 135 | 136 | let rimraf; | |
| 136 | 137 | let rimrafSync; | |
| 138 | + let DOMException; | ||
| 139 | + | ||
| 140 | + const lazyDOMException = hideStackFrames((message, name) => { | ||
| 141 | + if (DOMException === undefined) | ||
| 142 | + DOMException = internalBinding('messaging').DOMException; | ||
| 143 | + return new DOMException(message, name); | ||
| 144 | + }); | ||
| 137 | 145 | ||
| 138 | 146 | // These have to be separate because of how graceful-fs happens to do it's | |
| 139 | 147 | // monkeypatching. | |
@@ -1425,7 +1433,11 @@ function lutimesSync(path, atime, mtime) { | |||
| 1425 | 1433 | handleErrorFromBinding(ctx); | |
| 1426 | 1434 | } | |
| 1427 | 1435 | ||
| 1428 | - function writeAll(fd, isUserFd, buffer, offset, length, callback) { | ||
| 1436 | + function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) { | ||
| 1437 | + if (signal?.aborted) { | ||
| 1438 | + callback(lazyDOMException('The operation was aborted', 'AbortError')); | ||
| 1439 | + return; | ||
| 1440 | + } | ||
| 1429 | 1441 | // write(fd, buffer, offset, length, position, callback) | |
| 1430 | 1442 | fs.write(fd, buffer, offset, length, null, (writeErr, written) => { | |
| 1431 | 1443 | if (writeErr) { | |
@@ -1445,7 +1457,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, callback) { | |||
| 1445 | 1457 | } else { | |
| 1446 | 1458 | offset += written; | |
| 1447 | 1459 | length -= written; | |
| 1448 | - writeAll(fd, isUserFd, buffer, offset, length, callback); | ||
| 1460 | + writeAll(fd, isUserFd, buffer, offset, length, signal, callback); | ||
| 1449 | 1461 | } | |
| 1450 | 1462 | }); | |
| 1451 | 1463 | } | |
@@ -1462,16 +1474,22 @@ function writeFile(path, data, options, callback) { | |||
| 1462 | 1474 | ||
| 1463 | 1475 | if (isFd(path)) { | |
| 1464 | 1476 | const isUserFd = true; | |
| 1465 | - writeAll(path, isUserFd, data, 0, data.byteLength, callback); | ||
| 1477 | + const signal = options.signal; | ||
| 1478 | + writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback); | ||
| 1466 | 1479 | return; | |
| 1467 | 1480 | } | |
| 1468 | 1481 | ||
| 1482 | + if (options.signal?.aborted) { | ||
| 1483 | + callback(lazyDOMException('The operation was aborted', 'AbortError')); | ||
| 1484 | + return; | ||
| 1485 | + } | ||
| 1469 | 1486 | fs.open(path, flag, options.mode, (openErr, fd) => { | |
| 1470 | 1487 | if (openErr) { | |
| 1471 | 1488 | callback(openErr); | |
| 1472 | 1489 | } else { | |
| 1473 | 1490 | const isUserFd = false; | |
| 1474 | - writeAll(fd, isUserFd, data, 0, data.byteLength, callback); | ||
| 1491 | + const signal = options.signal; | ||
| 1492 | + writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback); | ||
| 1475 | 1493 | } | |
| 1476 | 1494 | }); | |
| 1477 | 1495 | } | |
| 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)); | |
@@ -644,9 +647,12 @@ async function writeFile(path, data, options) { | |||
| 644 | 647 | } | |
| 645 | 648 | ||
| 646 | 649 | if (path instanceof FileHandle) | |
| 647 | - return writeFileHandle(path, data); | ||
| 650 | + return writeFileHandle(path, data, options.signal); | ||
| 648 | 651 | ||
| 649 | 652 | const fd = await open(path, flag, options.mode); | |
| 653 | + if (options.signal?.aborted) { | ||
| 654 | + throw new lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 655 | + } | ||
| 650 | 656 | return PromisePrototypeFinally(writeFileHandle(fd, data), fd.close); | |
| 651 | 657 | } | |
| 652 | 658 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + // Flags: --experimental-abortcontroller | ||
| 1 | 2 | 'use strict'; | |
| 2 | 3 | ||
| 3 | 4 | const common = require('../common'); | |
@@ -11,6 +12,7 @@ const tmpDir = tmpdir.path; | |||
| 11 | 12 | tmpdir.refresh(); | |
| 12 | 13 | ||
| 13 | 14 | const dest = path.resolve(tmpDir, 'tmp.txt'); | |
| 15 | + const otherDest = path.resolve(tmpDir, 'tmp-2.txt'); | ||
| 14 | 16 | const buffer = Buffer.from('abc'.repeat(1000)); | |
| 15 | 17 | const buffer2 = Buffer.from('xyz'.repeat(1000)); | |
| 16 | 18 | ||
@@ -20,6 +22,15 @@ async function doWrite() { | |||
| 20 | 22 | assert.deepStrictEqual(data, buffer); | |
| 21 | 23 | } | |
| 22 | 24 | ||
| 25 | + async function doWriteWithCancel() { | ||
| 26 | + const controller = new AbortController(); | ||
| 27 | + const { signal } = controller; | ||
| 28 | + process.nextTick(() => controller.abort()); | ||
| 29 | + assert.rejects(fsPromises.writeFile(otherDest, buffer, { signal }), { | ||
| 30 | + name: 'AbortError' | ||
| 31 | + }); | ||
| 32 | + } | ||
| 33 | + | ||
| 23 | 34 | async function doAppend() { | |
| 24 | 35 | await fsPromises.appendFile(dest, buffer2); | |
| 25 | 36 | const data = fs.readFileSync(dest); | |
@@ -41,6 +52,7 @@ async function doReadWithEncoding() { | |||
| 41 | 52 | } | |
| 42 | 53 | ||
| 43 | 54 | doWrite() | |
| 55 | + .then(doWriteWithCancel) | ||
| 44 | 56 | .then(doAppend) | |
| 45 | 57 | .then(doRead) | |
| 46 | 58 | .then(doReadWithEncoding) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,7 @@ | |||
| 19 | 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
| 20 | 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 21 | 21 | ||
| 22 | + // Flags: --experimental-abortcontroller | ||
| 22 | 23 | 'use strict'; | |
| 23 | 24 | const common = require('../common'); | |
| 24 | 25 | const assert = require('assert'); | |
@@ -66,3 +67,32 @@ fs.open(filename4, 'w+', common.mustSucceed((fd) => { | |||
| 66 | 67 | })); | |
| 67 | 68 | })); | |
| 68 | 69 | })); | |
| 70 | + | ||
| 71 | + | ||
| 72 | + { | ||
| 73 | + // Test that writeFile is cancellable with an AbortSignal. | ||
| 74 | + // Before the operation has started | ||
| 75 | + const controller = new AbortController(); | ||
| 76 | + const signal = controller.signal; | ||
| 77 | + const filename3 = join(tmpdir.path, 'test3.txt'); | ||
| 78 | + | ||
| 79 | + fs.writeFile(filename3, s, { signal }, common.mustCall((err) => { | ||
| 80 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 81 | + })); | ||
| 82 | + | ||
| 83 | + controller.abort(); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + { | ||
| 87 | + // Test that writeFile is cancellable with an AbortSignal. | ||
| 88 | + // After the operation has started | ||
| 89 | + const controller = new AbortController(); | ||
| 90 | + const signal = controller.signal; | ||
| 91 | + const filename4 = join(tmpdir.path, 'test4.txt'); | ||
| 92 | + | ||
| 93 | + fs.writeFile(filename4, s, { signal }, common.mustCall((err) => { | ||
| 94 | + assert.strictEqual(err.name, 'AbortError'); | ||
| 95 | + })); | ||
| 96 | + | ||
| 97 | + process.nextTick(() => controller.abort()); | ||
| 98 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments