-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
fs: add support for async iterators to fs.writeFile
#38525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
785d76d
6f21783
c0b87d6
05a5728
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,8 @@ const { | |
StringPrototypeCharCodeAt, | ||
StringPrototypeIndexOf, | ||
StringPrototypeSlice, | ||
SymbolAsyncIterator, | ||
SymbolIterator, | ||
} = primordials; | ||
|
||
const { fs: constants } = internalBinding('constants'); | ||
|
@@ -85,6 +87,7 @@ const { | |
const { FSReqCallback } = binding; | ||
const { toPathIfFileURL } = require('internal/url'); | ||
const internalUtil = require('internal/util'); | ||
const { isCustomIterable } = require('internal/streams/utils'); | ||
const { | ||
constants: { | ||
kIoMaxLength, | ||
|
@@ -828,12 +831,12 @@ function write(fd, buffer, offset, length, position, callback) { | |
} else { | ||
position = length; | ||
} | ||
length = 'utf8'; | ||
length = length || 'utf8'; | ||
} | ||
|
||
const str = String(buffer); | ||
validateEncoding(str, length); | ||
callback = maybeCallback(position); | ||
callback = maybeCallback(callback || position); | ||
|
||
const req = new FSReqCallback(); | ||
req.oncomplete = wrapper; | ||
|
@@ -2039,7 +2042,8 @@ function lutimesSync(path, atime, mtime) { | |
handleErrorFromBinding(ctx); | ||
} | ||
|
||
function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) { | ||
function writeAll( | ||
fd, isUserFd, buffer, offset, length, signal, encoding, callback) { | ||
if (signal?.aborted) { | ||
const abortError = new AbortError(); | ||
if (isUserFd) { | ||
|
@@ -2051,16 +2055,16 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) { | |
} | ||
return; | ||
} | ||
// write(fd, buffer, offset, length, position, callback) | ||
|
||
if (isCustomIterable(buffer)) { | ||
writeAllCustomIterable( | ||
fd, isUserFd, buffer, offset, length, signal, encoding, callback) | ||
.catch((reason) => { throw reason; }); | ||
Linkgoron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
fs.write(fd, buffer, offset, length, null, (writeErr, written) => { | ||
if (writeErr) { | ||
if (isUserFd) { | ||
callback(writeErr); | ||
} else { | ||
fs.close(fd, (err) => { | ||
callback(aggregateTwoErrors(err, writeErr)); | ||
}); | ||
} | ||
handleWriteAllErrorCallback(fd, isUserFd, writeErr, callback); | ||
} else if (written === length) { | ||
if (isUserFd) { | ||
callback(null); | ||
|
@@ -2070,11 +2074,43 @@ function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) { | |
} else { | ||
offset += written; | ||
length -= written; | ||
writeAll(fd, isUserFd, buffer, offset, length, signal, callback); | ||
writeAll( | ||
fd, isUserFd, buffer, offset, length, signal, encoding, callback); | ||
} | ||
}); | ||
} | ||
|
||
async function writeAllCustomIterable( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mcollina Thanks for your suggestion 👍 . I'm curious about the advantages of having a promisified version of |
||
fd, isUserFd, buffer, offset, length, signal, encoding, callback) { | ||
const result = await buffer.next(); | ||
if (result.done) { | ||
fs.close(fd, callback); | ||
Linkgoron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
const resultValue = result.value.toString(); | ||
Linkgoron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fs.write(fd, resultValue, undefined, | ||
isArrayBufferView(buffer) ? resultValue.byteLength : encoding, | ||
null, (writeErr, _) => { | ||
if (writeErr) { | ||
handleWriteAllErrorCallback(fd, isUserFd, writeErr, callback); | ||
} else { | ||
writeAll(fd, isUserFd, buffer, offset, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that there's a mistake here that also exists in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I think that it makes more sense to call |
||
length, signal, encoding, callback); | ||
} | ||
} | ||
); | ||
} | ||
|
||
function handleWriteAllErrorCallback(fd, isUserFd, writeErr, callback) { | ||
if (isUserFd) { | ||
callback(writeErr); | ||
} else { | ||
fs.close(fd, (err) => { | ||
callback(aggregateTwoErrors(err, writeErr)); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Asynchronously writes data to the file. | ||
* @param {string | Buffer | URL | number} path | ||
|
@@ -2093,15 +2129,20 @@ function writeFile(path, data, options, callback) { | |
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); | ||
const flag = options.flag || 'w'; | ||
|
||
if (!isArrayBufferView(data)) { | ||
if (!isArrayBufferView(data) && !isCustomIterable(data)) { | ||
validateStringAfterArrayBufferView(data, 'data'); | ||
data = Buffer.from(String(data), options.encoding || 'utf8'); | ||
} | ||
|
||
if (isCustomIterable(data)) { | ||
data = data[SymbolIterator]?.() ?? data[SymbolAsyncIterator]?.(); | ||
} | ||
|
||
if (isFd(path)) { | ||
const isUserFd = true; | ||
const signal = options.signal; | ||
writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback); | ||
writeAll(path, isUserFd, data, | ||
0, data.byteLength, signal, options.encoding, callback); | ||
return; | ||
} | ||
|
||
|
@@ -2114,7 +2155,8 @@ function writeFile(path, data, options, callback) { | |
} else { | ||
const isUserFd = false; | ||
const signal = options.signal; | ||
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback); | ||
writeAll(fd, isUserFd, data, | ||
0, data.byteLength, signal, options.encoding, callback); | ||
} | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const fs = require('fs'); | ||
const join = require('path').join; | ||
const { Readable } = require('stream'); | ||
|
||
const tmpdir = require('../common/tmpdir'); | ||
tmpdir.refresh(); | ||
|
||
{ | ||
const filenameIterable = join(tmpdir.path, 'testIterable.txt'); | ||
const iterable = { | ||
expected: 'abc', | ||
*[Symbol.iterator]() { | ||
yield 'a'; | ||
yield 'b'; | ||
yield 'c'; | ||
} | ||
}; | ||
|
||
fs.writeFile(filenameIterable, iterable, common.mustSucceed(() => { | ||
const data = fs.readFileSync(filenameIterable, 'utf-8'); | ||
assert.strictEqual(iterable.expected, data); | ||
})); | ||
} | ||
|
||
{ | ||
const filenameBufferIterable = join(tmpdir.path, 'testBufferIterable.txt'); | ||
const bufferIterable = { | ||
expected: 'abc', | ||
*[Symbol.iterator]() { | ||
yield Buffer.from('a'); | ||
yield Buffer.from('b'); | ||
yield Buffer.from('c'); | ||
} | ||
}; | ||
|
||
fs.writeFile( | ||
filenameBufferIterable, bufferIterable, common.mustSucceed(() => { | ||
const data = fs.readFileSync(filenameBufferIterable, 'utf-8'); | ||
assert.strictEqual(bufferIterable.expected, data); | ||
}) | ||
); | ||
} | ||
|
||
|
||
{ | ||
const filenameAsyncIterable = join(tmpdir.path, 'testAsyncIterable.txt'); | ||
const asyncIterable = { | ||
expected: 'abc', | ||
*[Symbol.asyncIterator]() { | ||
yield 'a'; | ||
yield 'b'; | ||
yield 'c'; | ||
} | ||
}; | ||
|
||
fs.writeFile(filenameAsyncIterable, asyncIterable, common.mustSucceed(() => { | ||
const data = fs.readFileSync(filenameAsyncIterable, 'utf-8'); | ||
assert.strictEqual(asyncIterable.expected, data); | ||
})); | ||
} | ||
|
||
{ | ||
const filenameStream = join(tmpdir.path, 'testStream.txt'); | ||
const stream = Readable.from(['a', 'b', 'c']); | ||
const expected = 'abc'; | ||
|
||
fs.writeFile(filenameStream, stream, common.mustSucceed(() => { | ||
const data = fs.readFileSync(filenameStream, 'utf-8'); | ||
assert.strictEqual(expected, data); | ||
})); | ||
} | ||
|
||
{ | ||
const filenameStreamWithEncoding = | ||
join(tmpdir.path, 'testStreamWithEncoding.txt'); | ||
const stream = Readable.from(['ümlaut', ' ', 'sechzig']); | ||
const expected = 'ümlaut sechzig'; | ||
|
||
fs.writeFile( | ||
filenameStreamWithEncoding, stream, 'latin1', common.mustSucceed(() => { | ||
const data = fs.readFileSync(filenameStreamWithEncoding, 'latin1'); | ||
assert.strictEqual(expected, data); | ||
}) | ||
); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.