Repository navigation
fs: .write(options) support #41666
Description
Activity
- addedfeature requestIssues requesting new Node.js features.Issues requesting new Node.js features.
on Jan 23, 2022 - addedfsIssues and PRs related to file-system APIs and the fs module.Issues and PRs related to file-system APIs and the fs module.
on Jan 23, 2022 Hi @LiviaMedeiros, would you be interested sending a PR implementing this? I'm not sure we can get rid of the
toStringspecial casing – that depends how widespread its usage is in the ecosystem (well if it is indeed broken, we can assume no one is using it) – but the rest sounds feasible.Yes, I'll probably work on this; but it still requires further confirmation if it's safe to change.
Supporting both named arguments and "stringable" objects would look ambiguous for their intersection:
// this one might be interpreted as string by documented logic, but it shouldn't const dataSlice = { buffer: new Uint8Array(4), position: 0x40, toString() { return String.fromCharCode(...this.buffer); } } // this one won't be interpreted as string because it inherits .toString() from prototype const someText = new String('someText'); // what is it and would it be obvious enough? const sameText = Object.assign(someText, { buffer: ['iAmNotBuffer', 'justSomeAssignedData'] });
After proper testing, it seems that
.writeSyncfrom Synchronous API has the same issue, but.writefrom Callback API works as documented.Promises API:
node/lib/internal/fs/promises.js
Lines 586 to 589 in a8afe26
validateStringAfterArrayBufferView(buffer, 'buffer'); validateEncoding(buffer, length); const bytesWritten = (await binding.writeString(handle.fd, buffer, offset, length, kUsePromises)) || 0; Synchronous API:
Lines 876 to 882 in a8afe26
validateStringAfterArrayBufferView(buffer, 'buffer'); validateEncoding(buffer, length); if (offset === undefined) offset = null; result = binding.writeString(fd, buffer, offset, length, undefined, ctx); Callback API:
Lines 824 to 842 in a8afe26
validateStringAfterArrayBufferView(buffer, 'buffer'); if (typeof position !== 'function') { if (typeof offset === 'function') { position = offset; offset = null; } else { position = length; } length = 'utf8'; } const str = String(buffer); validateEncoding(str, length); callback = maybeCallback(position); const req = new FSReqCallback(); req.oncomplete = wrapper; return binding.writeString(fd, str, offset, length, req); validateStringAfterArrayBufferViewjust checks if it's a string or an object with ownProperty function.toString
Callback version also performs type conversion before sending it to binding.writeStringWould it make sense to rework only Promises and Synchronous versions, and keep Callback "as is" for now?
- added 2 commits that reference this issue
on Jan 25, 2022 - added a commit that references this issue
on Feb 27, 2022 - added a commit that references this issue
on Apr 4, 2022 12 remaining items
- added a commit that references this issue
on May 30, 2022 - added 2 commits that reference this issue
on May 31, 2022 - added a commit that references this issue
on Jun 27, 2022 - added 2 commits that reference this issue
on Jul 12, 2022 - added 2 commits that reference this issue
on Jul 31, 2022 - added 2 commits that reference this issue
on Oct 10, 2022
What is the problem this feature will solve?
In
fssubsystem, there are forms of.readmethods, accepting Object as argument: filehandle.read, fs.read, fs.readSync.They allow to skip optional
offsetand/orlength, and to use convenient reusable objects, e.g.const webpSize = {buffer: new Uint32Array(1), position: 0x4}However,
.writecounterparts don't support that. So instead of using "named arguments" and reusing the same objects (e.g.await fh.write(webpSize);after modifying buffer contents), we have to write a wrapper function or something like:...which doesn't feel right.
What is the feature you are proposing to solve the problem?
Adding:
filehandle.write(options)to Promises API (FileHandle class)fs.write(fd, options, callback)to Callback APIfs.writeSync(fd, options)to Synchronous APISkipping
optionswould do the same as passing{}which meansbuffer === undefined, so making them optional is redundant.Doing nothing or "writing 0 bytes" by default contradicts with async reading functions: they create 16KB buffer.
Rough patch for Promises API:
Of course, this will break current code, because if
bufferisn't an ArrayBufferView, Node.js assumesfilehandle.write(string[, position[, encoding]]).Or it implements this: "If
bufferis a plain object, it must have an own (not inherited)toStringfunction property."?Or this: "If
stringis not a string, or an object with an owntoStringfunction property, the promise is rejected with an error."?So here goes the important part: current code is already broken.
Correctly handled error:
Again but with
toString:Same outcome in v16.13.1.
While ".toString() must be an own property, not inherited" rule might successfully prevent from writing random
[object Object]s everywhere, it also makes stringifying objects barely usable:Rather than fixing this and allowing weird and potentially dangerous implicit
.toString()calls, I suggest to use it as opportunity to change behaviour for object to "named arguments", because it won't break too much.Thank you.
What alternatives have you considered?
Additional segregation between methods for buffers and strings, e.g. by introducing
.writeString,.writeStringSyncConsiderable, but devastatingly breaking change.