-
-
Notifications
You must be signed in to change notification settings - Fork 32k
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 all commits
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 |
---|---|---|
|
@@ -32,6 +32,7 @@ const { | |
ArrayPrototypePush, | ||
BigIntPrototypeToString, | ||
MathMax, | ||
MathMin, | ||
Number, | ||
ObjectCreate, | ||
ObjectDefineProperties, | ||
|
@@ -44,6 +45,8 @@ const { | |
StringPrototypeCharCodeAt, | ||
StringPrototypeIndexOf, | ||
StringPrototypeSlice, | ||
SymbolAsyncIterator, | ||
SymbolIterator, | ||
} = primordials; | ||
|
||
const { fs: constants } = internalBinding('constants'); | ||
|
@@ -85,10 +88,12 @@ const { | |
const { FSReqCallback } = binding; | ||
const { toPathIfFileURL } = require('internal/url'); | ||
const internalUtil = require('internal/util'); | ||
const { isCustomIterable } = require('internal/streams/utils'); | ||
const { | ||
constants: { | ||
kIoMaxLength, | ||
kMaxUserId, | ||
kWriteFileMaxChunkSize, | ||
}, | ||
copyObject, | ||
Dirent, | ||
|
@@ -828,12 +833,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,28 +2044,24 @@ 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) { | ||
callback(abortError); | ||
} else { | ||
fs.close(fd, (err) => { | ||
callback(aggregateTwoErrors(err, abortError)); | ||
handleWriteAllErrorCallback(fd, isUserFd, new AbortError(), callback); | ||
return; | ||
} | ||
|
||
if (isCustomIterable(buffer)) { | ||
writeAllCustomIterable( | ||
fd, isUserFd, buffer, offset, length, signal, encoding, callback) | ||
.catch((reason) => { | ||
handleWriteAllErrorCallback(fd, isUserFd, reason, callback); | ||
}); | ||
} | ||
return; | ||
} | ||
// write(fd, buffer, offset, length, position, callback) | ||
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 +2071,82 @@ 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) { | ||
if (signal?.aborted) { | ||
handleWriteAllErrorCallback(fd, isUserFd, new AbortError(), callback); | ||
return; | ||
} | ||
|
||
const result = await buffer.next(); | ||
if (result.done) { | ||
if (isUserFd) { | ||
callback(null); | ||
} else { | ||
fs.close(fd, callback); | ||
} | ||
return; | ||
} | ||
if (signal?.aborted) { | ||
handleWriteAllErrorCallback(fd, isUserFd, new AbortError(), callback); | ||
return; | ||
} | ||
const resultValue = isArrayBufferView(result.value) ? | ||
Linkgoron marked this conversation as resolved.
Show resolved
Hide resolved
|
||
result.value : Buffer.from(String(result.value), encoding); | ||
const remaining = resultValue.byteLength; | ||
const writeSize = MathMin(kWriteFileMaxChunkSize, remaining); | ||
fs.write(fd, resultValue, resultValue.byteLength - remaining, writeSize, | ||
null, (writeErr, written) => { | ||
handleWriteAllCustomIterableCallback( | ||
fd, isUserFd, buffer, resultValue, | ||
resultValue.byteLength - remaining, writeSize, | ||
signal, encoding, writeErr, remaining, written, callback); | ||
} | ||
); | ||
} | ||
|
||
function handleWriteAllCustomIterableCallback(fd, isUserFd, buffer, resultValue, | ||
offset, length, signal, encoding, | ||
writeErr, remaining, written, | ||
callback) { | ||
if (writeErr) { | ||
handleWriteAllErrorCallback(fd, isUserFd, writeErr, callback); | ||
return; | ||
} | ||
|
||
remaining -= written; | ||
if (remaining > 0) { | ||
const writeSize = MathMin(kWriteFileMaxChunkSize, remaining); | ||
fs.write(fd, resultValue, | ||
resultValue.byteLength - remaining, writeSize, | ||
null, (writeErr, written) => { | ||
handleWriteAllCustomIterableCallback( | ||
fd, isUserFd, buffer, resultValue, offset, length, | ||
signal, encoding, writeErr, remaining, written, callback); | ||
}); | ||
return; | ||
} | ||
|
||
writeAllCustomIterable( | ||
fd, isUserFd, buffer, offset, 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 +2165,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 +2191,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); | ||
} | ||
}); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would schedule this via
process.nextTick
. If this throws synchronously for whatever reason, it will lead to an unhandled rejection.