Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fixup! fs: add support for async iterators to fs.writeFile
Fixes: #38075
  • Loading branch information
HiroyukiYagihashi committed May 9, 2021
commit 6f2178394d18b2521b927aa52560174750dcee67
34 changes: 19 additions & 15 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2045,21 +2045,16 @@ function lutimesSync(path, atime, mtime) {
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) => { throw reason; });
.catch((reason) => {
handleWriteAllErrorCallback(fd, isUserFd, reason, callback);
Copy link
Copy Markdown
Member

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.

});
return;
}
fs.write(fd, buffer, offset, length, null, (writeErr, written) => {
Expand All @@ -2082,20 +2077,29 @@ function writeAll(

async function writeAllCustomIterable(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use async with a callback. Mixing those two can only lead to bugs. I would recommend having a promisified version of fs.write() and just use async/await.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 fs.write() and using async/await. Could you elaborate on why this approach might be better than mixing async with a callback? I'm interested in understanding the potential benefits and how it could prevent bugs.

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) {
fs.close(fd, callback);
if (isUserFd) {
callback(null);
} else {
fs.close(fd, callback);
}
return;
}
const resultValue = result.value.toString();
fs.write(fd, resultValue, undefined,
isArrayBufferView(buffer) ? resultValue.byteLength : encoding,
const resultValue = isArrayBufferView(result.value) ?
Comment thread
Linkgoron marked this conversation as resolved.
result.value : Buffer.from(String(result.value), encoding);
fs.write(fd, resultValue, offset, resultValue.byteLength,
null, (writeErr, _) => {
if (writeErr) {
handleWriteAllErrorCallback(fd, isUserFd, writeErr, callback);
} else {
writeAll(fd, isUserFd, buffer, offset,
length, signal, encoding, callback);
writeAllCustomIterable(fd, isUserFd, buffer, offset, length,
signal, encoding, callback);
}
}
);
Expand Down
64 changes: 64 additions & 0 deletions test/parallel/test-fs-write-file-async-iterators.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ tmpdir.refresh();
);
}

{
const filenameBufferIterableWithEncoding =
join(tmpdir.path, 'testBufferIterableWithEncoding.txt');
const bufferIterableWithEncoding = {
expected: 'ümlaut sechzig',
*[Symbol.iterator]() {
yield Buffer.from('ümlaut');
yield Buffer.from(' ');
yield Buffer.from('sechzig');
}
};

fs.writeFile(
filenameBufferIterableWithEncoding,
bufferIterableWithEncoding, common.mustSucceed(() => {
const data = fs.readFileSync(filenameBufferIterableWithEncoding, 'utf-8');
assert.strictEqual(bufferIterableWithEncoding.expected, data);
})
);
}

{
const filenameAsyncIterable = join(tmpdir.path, 'testAsyncIterable.txt');
Expand Down Expand Up @@ -86,3 +106,47 @@ tmpdir.refresh();
})
);
}

{
const controller = new AbortController();
const signal = controller.signal;
const filenameIterableAbort = join(tmpdir.path, 'testIterableAbort.txt');
const iterable = {
expected: 'abc',
*[Symbol.iterator]() {
yield 'a';
yield 'b';
yield 'c';
}
};


fs.writeFile(filenameIterableAbort,
iterable, { signal }, common.mustCall((err) => {
assert.strictEqual(err.name, 'AbortError');
}));

controller.abort();
}

{
const controller = new AbortController();
const signal = controller.signal;
const filenameAsyncIterableAbort =
join(tmpdir.path, 'testAsyncIterableAbort.txt');
const asyncIterable = {
expected: 'abc',
*[Symbol.asyncIterator]() {
yield 'a';
yield 'b';
yield 'c';
}
};

fs.writeFile(filenameAsyncIterableAbort,
asyncIterable, { signal }, common.mustCall((err) => {
assert.strictEqual(err.name, 'AbortError');
}));

process.nextTick(() => controller.abort());
}