fs: move fs.promises API to fs/promises

PR-URL: https://github.com/nodejs/node/pull/18777
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Michaël Zasso 2018-02-14 10:04:22 +01:00
parent 2620358624
commit 513d939720
8 changed files with 581 additions and 530 deletions

View File

@ -1,7 +1,7 @@
'use strict';
const common = require('../common');
const fs = require('fs');
const fsPromises = require('fs/promises');
const bench = common.createBenchmark(main, {
n: [20e4],
@ -10,11 +10,11 @@ const bench = common.createBenchmark(main, {
async function run(n, statType) {
const arg = statType === 'fstat' ?
await fs.promises.open(__filename, 'r') : __filename;
await fsPromises.open(__filename, 'r') : __filename;
let remaining = n;
bench.start();
while (remaining-- > 0)
await fs.promises[statType](arg);
await fsPromises[statType](arg);
bench.end(n);
if (typeof arg.close === 'function')

View File

@ -3231,9 +3231,9 @@ Synchronous versions of [`fs.write()`][]. Returns the number of bytes written.
> Stability: 1 - Experimental
The `fs.promises` API provides an alternative set of asynchronous file system
The `fs/promises` API provides an alternative set of asynchronous file system
methods that return `Promise` objects rather than using callbacks. The
API is accessible via `fs.promises`.
API is accessible via `require('fs/promises)`.
### class: FileHandle
<!-- YAML
@ -3247,11 +3247,11 @@ in that, if the `FileHandle` is not explicitly closed using the
and will emit a process warning, thereby helping to prevent memory leaks.
Instances of the `FileHandle` object are created internally by the
`fs.promises.open()` method.
`fsPromises.open()` method.
Unlike callback-based such as `fs.fstat()`, `fs.fchown()`, `fs.fchmod()`,
`fs.ftruncate()`, `fs.read()`, and `fs.write()`, operations -- all of which
use a simple numeric file descriptor, all `fs.promises.*` variations use the
use a simple numeric file descriptor, all `fsPromises.*` variations use the
`FileHandle` class in order to help protect against accidental leaking of
unclosed file descriptors after a `Promise` is resolved or rejected.
@ -3317,7 +3317,7 @@ Closes the file descriptor.
async function openAndClose() {
let filehandle;
try {
filehandle = await fs.promises.open('thefile.txt', 'r');
filehandle = await fsPromises.open('thefile.txt', 'r');
} finally {
if (filehandle !== undefined)
await filehandle.close();
@ -3378,7 +3378,7 @@ object. Otherwise, the data will be a string.
If `options` is a string, then it specifies the encoding.
When the `path` is a directory, the behavior of `fs.promises.readFile()` is
When the `path` is a directory, the behavior of `fsPromises.readFile()` is
platform-specific. On macOS, Linux, and Windows, the promise will be rejected
with an error. On FreeBSD, a representation of the directory's contents will be
returned.
@ -3422,8 +3422,8 @@ console.log(fs.readFileSync('temp.txt', 'utf8'));
// Prints: Node.js
async function doTruncate() {
const fd = await fs.promises.open('temp.txt', 'r+');
await fs.promises.ftruncate(fd, 4);
const fd = await fsPromises.open('temp.txt', 'r+');
await fsPromises.ftruncate(fd, 4);
console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints: Node
}
@ -3438,8 +3438,8 @@ console.log(fs.readFileSync('temp.txt', 'utf8'));
// Prints: Node.js
async function doTruncate() {
const fd = await fs.promises.open('temp.txt', 'r+');
await fs.promises.ftruncate(fd, 10);
const fd = await fsPromises.open('temp.txt', 'r+');
await fsPromises.ftruncate(fd, 10);
console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints Node.js\0\0\0
}
@ -3518,7 +3518,7 @@ The `FileHandle` has to support writing.
It is unsafe to use `filehandle.writeFile()` multiple times on the same file
without waiting for the `Promise` to be resolved (or rejected).
### fs.promises.access(path[, mode])
### fsPromises.access(path[, mode])
<!-- YAML
added: REPLACEME
-->
@ -3547,18 +3547,18 @@ with an `Error` object. The following example checks if the file
`/etc/passwd` can be read and written by the current process.
```js
fs.promises.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK)
fsPromises.access('/etc/passwd', fs.constants.R_OK | fs.constants.W_OK)
.then(() => console.log('can access'))
.catch(() => console.error('cannot access'));
```
Using `fs.promises.access()` to check for the accessibility of a file before
calling `fs.promises.open()` is not recommended. Doing so introduces a race
Using `fsPromises.access()` to check for the accessibility of a file before
calling `fsPromises.open()` is not recommended. Doing so introduces a race
condition, since other processes may change the file's state between the two
calls. Instead, user code should open/read/write the file directly and handle
the error raised if the file is not accessible.
### fs.promises.appendFile(file, data[, options])
### fsPromises.appendFile(file, data[, options])
<!-- YAML
added: REPLACEME
-->
@ -3578,9 +3578,9 @@ resolved with no arguments upon success.
If `options` is a string, then it specifies the encoding.
The `file` may be specified as a `FileHandle` that has been opened
for appending (using `fs.promises.open()`).
for appending (using `fsPromises.open()`).
### fs.promises.chmod(path, mode)
### fsPromises.chmod(path, mode)
<!-- YAML
added: REPLACEME
-->
@ -3592,7 +3592,7 @@ added: REPLACEME
Changes the permissions of a file then resolves the `Promise` with no
arguments upon succces.
### fs.promises.chown(path, uid, gid)
### fsPromises.chown(path, uid, gid)
<!-- YAML
added: REPLACEME
-->
@ -3605,7 +3605,7 @@ added: REPLACEME
Changes the ownership of a file then resolves the `Promise` with no arguments
upon success.
### fs.promises.copyFile(src, dest[, flags])
### fsPromises.copyFile(src, dest[, flags])
<!-- YAML
added: REPLACEME
-->
@ -3632,7 +3632,7 @@ Example:
const fs = require('fs');
// destination.txt will be created or overwritten by default.
fs.promises.copyFile('source.txt', 'destination.txt')
fsPromises.copyFile('source.txt', 'destination.txt')
.then(() => console.log('source.txt was copied to destination.txt'))
.catch(() => console.log('The file could not be copied'));
```
@ -3645,12 +3645,12 @@ const fs = require('fs');
const { COPYFILE_EXCL } = fs.constants;
// By using COPYFILE_EXCL, the operation will fail if destination.txt exists.
fs.promises.copyFile('source.txt', 'destination.txt', COPYFILE_EXCL)
fsPromises.copyFile('source.txt', 'destination.txt', COPYFILE_EXCL)
.then(() => console.log('source.txt was copied to destination.txt'))
.catch(() => console.log('The file could not be copied'));
```
### fs.promises.fchmod(filehandle, mode)
### fsPromises.fchmod(filehandle, mode)
<!-- YAML
added: REPLACEME
-->
@ -3662,7 +3662,7 @@ added: REPLACEME
Asynchronous fchmod(2). The `Promise` is resolved with no arguments upon
success.
### fs.promises.fchown(filehandle, uid, gid)
### fsPromises.fchown(filehandle, uid, gid)
<!-- YAML
added: REPLACEME
-->
@ -3675,7 +3675,7 @@ added: REPLACEME
Changes the ownership of the file represented by `filehandle` then resolves
the `Promise` with no arguments upon success.
### fs.promises.fdatasync(filehandle)
### fsPromises.fdatasync(filehandle)
<!-- YAML
added: REPLACEME
-->
@ -3686,7 +3686,7 @@ added: REPLACEME
Asynchronous fdatasync(2). The `Promise` is resolved with no arguments upon
success.
### fs.promises.fstat(filehandle)
### fsPromises.fstat(filehandle)
<!-- YAML
added: REPLACEME
-->
@ -3696,7 +3696,7 @@ added: REPLACEME
Retrieves the [`fs.Stats`][] for the given `filehandle`.
### fs.promises.fsync(filehandle)
### fsPromises.fsync(filehandle)
<!-- YAML
added: REPLACEME
-->
@ -3707,7 +3707,7 @@ added: REPLACEME
Asynchronous fsync(2). The `Promise` is resolved with no arguments upon
success.
### fs.promises.ftruncate(filehandle[, len])
### fsPromises.ftruncate(filehandle[, len])
<!-- YAML
added: REPLACEME
-->
@ -3730,8 +3730,8 @@ console.log(fs.readFileSync('temp.txt', 'utf8'));
// Prints: Node.js
async function doTruncate() {
const fd = await fs.promises.open('temp.txt', 'r+');
await fs.promises.ftruncate(fd, 4);
const fd = await fsPromises.open('temp.txt', 'r+');
await fsPromises.ftruncate(fd, 4);
console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints: Node
}
@ -3746,8 +3746,8 @@ console.log(fs.readFileSync('temp.txt', 'utf8'));
// Prints: Node.js
async function doTruncate() {
const fd = await fs.promises.open('temp.txt', 'r+');
await fs.promises.ftruncate(fd, 10);
const fd = await fsPromises.open('temp.txt', 'r+');
await fsPromises.ftruncate(fd, 10);
console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints Node.js\0\0\0
}
@ -3756,7 +3756,7 @@ doTruncate().catch(console.error);
The last three bytes are null bytes ('\0'), to compensate the over-truncation.
### fs.promises.futimes(filehandle, atime, mtime)
### fsPromises.futimes(filehandle, atime, mtime)
<!-- YAML
added: REPLACEME
-->
@ -3772,7 +3772,7 @@ Change the file system timestamps of the object referenced by the supplied
This function does not work on AIX versions before 7.1, it will resolve the
`Promise` with an error using code `UV_ENOSYS`.
### fs.promises.lchmod(path, mode)
### fsPromises.lchmod(path, mode)
<!-- YAML
deprecated: REPLACEME
-->
@ -3784,7 +3784,7 @@ deprecated: REPLACEME
Changes the permissions on a symbolic link then resolves the `Promise` with
no arguments upon success. This method is only implemented on macOS.
### fs.promises.lchown(path, uid, gid)
### fsPromises.lchown(path, uid, gid)
<!-- YAML
deprecated: REPLACEME
-->
@ -3797,7 +3797,7 @@ deprecated: REPLACEME
Changes the ownership on a symbolic link then resolves the `Promise` with
no arguments upon success. This method is only implemented on macOS.
### fs.promises.link(existingPath, newPath)
### fsPromises.link(existingPath, newPath)
<!-- YAML
added: REPLACEME
-->
@ -3808,7 +3808,7 @@ added: REPLACEME
Asynchronous link(2). The `Promise` is resolved with no arguments upon success.
### fs.promises.lstat(path)
### fsPromises.lstat(path)
<!-- YAML
added: REPLACEME
-->
@ -3819,7 +3819,7 @@ added: REPLACEME
Asynchronous lstat(2). The `Promise` is resolved with the [`fs.Stats`][] object
for the given symbolic link `path`.
### fs.promises.mkdir(path[, mode])
### fsPromises.mkdir(path[, mode])
<!-- YAML
added: REPLACEME
-->
@ -3831,7 +3831,7 @@ added: REPLACEME
Asynchronously creates a directory then resolves the `Promise` with no
arguments upon success.
### fs.promises.mkdtemp(prefix[, options])
### fsPromises.mkdtemp(prefix[, options])
<!-- YAML
added: REPLACEME
-->
@ -3851,7 +3851,7 @@ object with an `encoding` property specifying the character encoding to use.
Example:
```js
fs.promises.mkdtemp(path.join(os.tmpdir(), 'foo-'))
fsPromises.mkdtemp(path.join(os.tmpdir(), 'foo-'))
.catch(console.error);
```
@ -3861,7 +3861,7 @@ intention is to create a temporary directory *within* `/tmp`, the `prefix`
*must* end with a trailing platform-specific path separator
(`require('path').sep`).
### fs.promises.open(path, flags[, mode])
### fsPromises.open(path, flags[, mode])
<!-- YAML
added: REPLACEME
-->
@ -3889,7 +3889,7 @@ An exception occurs if the file does not exist.
the potentially stale local cache. It has a very real impact on I/O
performance so using this flag is not recommended unless it is needed.
Note that this does not turn `fs.promises.open()` into a synchronous blocking
Note that this does not turn `fsPromises.open()` into a synchronous blocking
call.
* `'w'` - Open file for writing.
@ -3929,7 +3929,7 @@ On Linux, positional writes don't work when the file is opened in append mode.
The kernel ignores the position argument and always appends the data to
the end of the file.
The behavior of `fs.promises.open()` is platform-specific for some
The behavior of `fsPromises.open()` is platform-specific for some
flags. As such, opening a directory on macOS and Linux with the `'a+'` flag will
return an error. In contrast, on Windows and FreeBSD, a `FileHandle` will be
returned.
@ -3940,11 +3940,11 @@ a colon, Node.js will open a file system stream, as described by
[this MSDN page][MSDN-Using-Streams].
*Note:* On Windows, opening an existing hidden file using the `w` flag (e.g.
using `fs.promises.open()`) will fail with `EPERM`. Existing hidden
using `fsPromises.open()`) will fail with `EPERM`. Existing hidden
files can be opened for writing with the `r+` flag. A call to
`fs.promises.ftruncate()` can be used to reset the file contents.
`fsPromises.ftruncate()` can be used to reset the file contents.
### fs.promises.read(filehandle, buffer, offset, length, position)
### fsPromises.read(filehandle, buffer, offset, length, position)
<!-- YAML
added: REPLACEME
-->
@ -3973,7 +3973,7 @@ Following successful read, the `Promise` is resolved with an object with a
`bytesRead` property specifying the number of bytes read, and a `buffer` property
that is a reference to the passed in `buffer` argument.
### fs.promises.readdir(path[, options])
### fsPromises.readdir(path[, options])
<!-- YAML
added: REPLACEME
-->
@ -3991,7 +3991,7 @@ object with an `encoding` property specifying the character encoding to use for
the filenames. If the `encoding` is set to `'buffer'`, the filenames returned
will be passed as `Buffer` objects.
### fs.promises.readFile(path[, options])
### fsPromises.readFile(path[, options])
<!-- YAML
added: REPLACEME
-->
@ -4010,14 +4010,14 @@ object. Otherwise, the data will be a string.
If `options` is a string, then it specifies the encoding.
When the `path` is a directory, the behavior of `fs.promises.readFile()` is
When the `path` is a directory, the behavior of `fsPromises.readFile()` is
platform-specific. On macOS, Linux, and Windows, the promise will be rejected
with an error. On FreeBSD, a representation of the directory's contents will be
returned.
Any specified `FileHandle` has to support reading.
### fs.promises.readlink(path[, options])
### fsPromises.readlink(path[, options])
<!-- YAML
added: REPLACEME
-->
@ -4035,7 +4035,7 @@ object with an `encoding` property specifying the character encoding to use for
the link path returned. If the `encoding` is set to `'buffer'`, the link path
returned will be passed as a `Buffer` object.
### fs.promises.realpath(path[, options])
### fsPromises.realpath(path[, options])
<!-- YAML
added: REPLACEME
-->
@ -4060,7 +4060,7 @@ On Linux, when Node.js is linked against musl libc, the procfs file system must
be mounted on `/proc` in order for this function to work. Glibc does not have
this restriction.
### fs.promises.rename(oldPath, newPath)
### fsPromises.rename(oldPath, newPath)
<!-- YAML
added: REPLACEME
-->
@ -4072,7 +4072,7 @@ added: REPLACEME
Renames `oldPath` to `newPath` and resolves the `Promise` with no arguments
upon success.
### fs.promises.rmdir(path)
### fsPromises.rmdir(path)
<!-- YAML
added: REPLACEME
-->
@ -4083,11 +4083,11 @@ added: REPLACEME
Removes the directory identified by `path` then resolves the `Promise` with
no arguments upon success.
Using `fs.promises.rmdir()` on a file (not a directory) results in the
Using `fsPromises.rmdir()` on a file (not a directory) results in the
`Promise` being rejected with an `ENOENT` error on Windows and an `ENOTDIR`
error on POSIX.
### fs.promises.stat(path)
### fsPromises.stat(path)
<!-- YAML
added: REPLACEME
-->
@ -4097,7 +4097,7 @@ added: REPLACEME
The `Promise` is resolved with the [`fs.Stats`][] object for the given `path`.
### fs.promises.symlink(target, path[, type])
### fsPromises.symlink(target, path[, type])
<!-- YAML
added: REPLACEME
-->
@ -4115,7 +4115,7 @@ The `type` argument is only used on Windows platforms and can be one of `'dir'`,
points require the destination path to be absolute. When using `'junction'`,
the `target` argument will automatically be normalized to absolute path.
### fs.promises.truncate(path[, len])
### fsPromises.truncate(path[, len])
<!-- YAML
added: REPLACEME
-->
@ -4127,7 +4127,7 @@ added: REPLACEME
Truncates the `path` then resolves the `Promise` with no arguments upon
success. The `path` *must* be a string or `Buffer`.
### fs.promises.unlink(path)
### fsPromises.unlink(path)
<!-- YAML
added: REPLACEME
-->
@ -4138,7 +4138,7 @@ added: REPLACEME
Asynchronous unlink(2). The `Promise` is resolved with no arguments upon
success.
### fs.promises.utimes(path, atime, mtime)
### fsPromises.utimes(path, atime, mtime)
<!-- YAML
added: REPLACEME
-->
@ -4157,7 +4157,7 @@ The `atime` and `mtime` arguments follow these rules:
- If the value can not be converted to a number, or is `NaN`, `Infinity` or
`-Infinity`, an `Error` will be thrown.
### fs.promises.write(filehandle, buffer[, offset[, length[, position]]])
### fsPromises.write(filehandle, buffer[, offset[, length[, position]]])
<!-- YAML
added: REPLACEME
-->
@ -4182,7 +4182,7 @@ an integer specifying the number of bytes to write.
should be written. If `typeof position !== 'number'`, the data will be written
at the current position. See pwrite(2).
It is unsafe to use `fs.promises.write()` multiple times on the same file
It is unsafe to use `fsPromises.write()` multiple times on the same file
without waiting for the `Promise` to be resolved (or rejected). For this
scenario, `fs.createWriteStream` is strongly recommended.
@ -4190,7 +4190,7 @@ On Linux, positional writes do not work when the file is opened in append mode.
The kernel ignores the position argument and always appends the data to
the end of the file.
### fs.promises.writeFile(file, data[, options])
### fsPromises.writeFile(file, data[, options])
<!-- YAML
added: REPLACEME
-->
@ -4214,7 +4214,7 @@ If `options` is a string, then it specifies the encoding.
Any specified `FileHandle` has to support writing.
It is unsafe to use `fs.promises.writeFile()` multiple times on the same file
It is unsafe to use `fsPromises.writeFile()` multiple times on the same file
without waiting for the `Promise` to be resolved (or rejected).

459
lib/fs.js
View File

@ -72,9 +72,6 @@ Object.defineProperty(exports, 'constants', {
value: constants
});
const kHandle = Symbol('handle');
const { kUsePromises } = binding;
const kMinPoolSpace = 128;
const { kMaxLength } = require('buffer');
@ -2284,459 +2281,3 @@ Object.defineProperty(fs, 'SyncWriteStream', {
set: internalUtil.deprecate((val) => { SyncWriteStream = val; },
'fs.SyncWriteStream is deprecated.', 'DEP0061')
});
// Promises API
class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
}
getAsyncId() {
return this[kHandle].getAsyncId();
}
get fd() {
return this[kHandle].fd;
}
appendFile(data, options) {
return promises.appendFile(this, data, options);
}
chmod(mode) {
return promises.fchmod(this, mode);
}
chown(uid, gid) {
return promises.fchown(this, uid, gid);
}
datasync() {
return promises.fdatasync(this);
}
sync() {
return promises.fsync(this);
}
read(buffer, offset, length, position) {
return promises.read(this, buffer, offset, length, position);
}
readFile(options) {
return promises.readFile(this, options);
}
stat() {
return promises.fstat(this);
}
truncate(len = 0) {
return promises.ftruncate(this, len);
}
utimes(atime, mtime) {
return promises.futimes(this, atime, mtime);
}
write(buffer, offset, length, position) {
return promises.write(this, buffer, offset, length, position);
}
writeFile(data, options) {
return promises.writeFile(this, data, options);
}
close() {
return this[kHandle].close();
}
}
function validateFileHandle(handle) {
if (!(handle instanceof FileHandle))
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'filehandle', 'FileHandle');
}
async function writeFileHandle(filehandle, data, options) {
let buffer = isUint8Array(data) ?
data : Buffer.from('' + data, options.encoding || 'utf8');
let remaining = buffer.length;
if (remaining === 0) return;
do {
const { bytesWritten } =
await promises.write(filehandle, buffer, 0,
Math.min(16384, buffer.length));
remaining -= bytesWritten;
buffer = buffer.slice(bytesWritten);
} while (remaining > 0);
}
async function readFileHandle(filehandle, options) {
const statFields = await binding.fstat(filehandle.fd, kUsePromises);
let size;
if ((statFields[1/*mode*/] & S_IFMT) === S_IFREG) {
size = statFields[8/*size*/];
} else {
size = 0;
}
if (size === 0)
return Buffer.alloc(0);
if (size > kMaxLength)
throw new errors.RangeError('ERR_BUFFER_TOO_LARGE');
const chunks = [];
const chunkSize = Math.min(size, 16384);
const buf = Buffer.alloc(chunkSize);
let read = 0;
do {
const { bytesRead, buffer } =
await promises.read(filehandle, buf, 0, buf.length);
read = bytesRead;
if (read > 0)
chunks.push(buffer.slice(0, read));
} while (read === chunkSize);
return Buffer.concat(chunks);
}
// All of the functions in fs.promises are defined as async in order to
// ensure that errors thrown cause promise rejections rather than being
// thrown synchronously
const promises = {
async access(path, mode = fs.F_OK) {
path = getPathFromURL(path);
validatePath(path);
mode = mode | 0;
return binding.access(pathModule.toNamespacedPath(path), mode,
kUsePromises);
},
async copyFile(src, dest, flags) {
src = getPathFromURL(src);
dest = getPathFromURL(dest);
validatePath(src, 'src');
validatePath(dest, 'dest');
flags = flags | 0;
return binding.copyFile(pathModule.toNamespacedPath(src),
pathModule.toNamespacedPath(dest),
flags, kUsePromises);
},
// Note that unlike fs.open() which uses numeric file descriptors,
// promises.open() uses the fs.FileHandle class.
async open(path, flags, mode) {
mode = modeNum(mode, 0o666);
path = getPathFromURL(path);
validatePath(path);
validateUint32(mode, 'mode');
return new FileHandle(
await binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags),
mode, kUsePromises));
},
async read(handle, buffer, offset, length, position) {
validateFileHandle(handle);
validateBuffer(buffer);
offset |= 0;
length |= 0;
if (length === 0)
return { bytesRead: length, buffer };
validateOffsetLengthRead(offset, length, buffer.length);
if (!isUint32(position))
position = -1;
const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
position, kUsePromises)) || 0;
return { bytesRead, buffer };
},
async write(handle, buffer, offset, length, position) {
validateFileHandle(handle);
if (buffer.length === 0)
return { bytesWritten: 0, buffer };
if (isUint8Array(buffer)) {
if (typeof offset !== 'number')
offset = 0;
if (typeof length !== 'number')
length = buffer.length - offset;
if (typeof position !== 'number')
position = null;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
const bytesWritten =
(await binding.writeBuffer(handle.fd, buffer, offset,
length, position, kUsePromises)) || 0;
return { bytesWritten, buffer };
}
if (typeof buffer !== 'string')
buffer += '';
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
length, kUsePromises)) || 0;
return { bytesWritten, buffer };
},
async rename(oldPath, newPath) {
oldPath = getPathFromURL(oldPath);
newPath = getPathFromURL(newPath);
validatePath(oldPath, 'oldPath');
validatePath(newPath, 'newPath');
return binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
},
async truncate(path, len = 0) {
return promises.ftruncate(await promises.open(path, 'r+'), len);
},
async ftruncate(handle, len = 0) {
validateFileHandle(handle);
validateLen(len);
len = Math.max(0, len);
return binding.ftruncate(handle.fd, len, kUsePromises);
},
async rmdir(path) {
path = getPathFromURL(path);
validatePath(path);
return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
},
async fdatasync(handle) {
validateFileHandle(handle);
return binding.fdatasync(handle.fd, kUsePromises);
},
async fsync(handle) {
validateFileHandle(handle);
return binding.fsync(handle.fd, kUsePromises);
},
async mkdir(path, mode) {
mode = modeNum(mode, 0o777);
path = getPathFromURL(path);
validatePath(path);
validateUint32(mode, 'mode');
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
},
async readdir(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
return binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
},
async readlink(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path, 'oldPath');
return binding.readlink(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
},
async symlink(target, path, type_) {
const type = (typeof type_ === 'string' ? type_ : null);
target = getPathFromURL(target);
path = getPathFromURL(path);
validatePath(target, 'target');
validatePath(path);
return binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path),
stringToSymlinkType(type),
kUsePromises);
},
async fstat(handle) {
validateFileHandle(handle);
return statsFromValues(await binding.fstat(handle.fd, kUsePromises));
},
async lstat(path) {
path = getPathFromURL(path);
validatePath(path);
return statsFromValues(
await binding.lstat(pathModule.toNamespacedPath(path), kUsePromises));
},
async stat(path) {
path = getPathFromURL(path);
validatePath(path);
return statsFromValues(
await binding.stat(pathModule.toNamespacedPath(path), kUsePromises));
},
async link(existingPath, newPath) {
existingPath = getPathFromURL(existingPath);
newPath = getPathFromURL(newPath);
validatePath(existingPath, 'existingPath');
validatePath(newPath, 'newPath');
return binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
},
async unlink(path) {
path = getPathFromURL(path);
validatePath(path);
return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
},
async fchmod(handle, mode) {
mode = modeNum(mode);
validateFileHandle(handle);
validateUint32(mode, 'mode');
if (mode < 0 || mode > 0o777)
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'mode');
return binding.fchmod(handle.fd, mode, kUsePromises);
},
async chmod(path, mode) {
path = getPathFromURL(path);
validatePath(path);
mode = modeNum(mode);
validateUint32(mode, 'mode');
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
},
async lchmod(path, mode) {
if (constants.O_SYMLINK !== undefined) {
const fd = await promises.open(path,
constants.O_WRONLY | constants.O_SYMLINK);
return promises.fchmod(fd, mode).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
},
async lchown(path, uid, gid) {
if (constants.O_SYMLINK !== undefined) {
const fd = await promises.open(path,
constants.O_WRONLY | constants.O_SYMLINK);
return promises.fchown(fd, uid, gid).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
},
async fchown(handle, uid, gid) {
validateFileHandle(handle);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.fchown(handle.fd, uid, gid, kUsePromises);
},
async chown(path, uid, gid) {
path = getPathFromURL(path);
validatePath(path);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.chown(pathModule.toNamespacedPath(path),
uid, gid, kUsePromises);
},
async utimes(path, atime, mtime) {
path = getPathFromURL(path);
validatePath(path);
return binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
kUsePromises);
},
async futimes(handle, atime, mtime) {
validateFileHandle(handle);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
return binding.futimes(handle.fd, atime, mtime, kUsePromises);
},
async realpath(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
return binding.realpath(path, options.encoding, kUsePromises);
},
async mkdtemp(prefix, options) {
options = getOptions(options, {});
if (!prefix || typeof prefix !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'prefix',
'string',
prefix);
}
nullCheck(prefix);
return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
},
async writeFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
const flag = options.flag || 'w';
if (path instanceof FileHandle)
return writeFileHandle(path, data, options);
const fd = await promises.open(path, flag, options.mode);
return writeFileHandle(fd, data, options).finally(fd.close.bind(fd));
},
async appendFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
options = copyObject(options);
options.flag = options.flag || 'a';
return promises.writeFile(path, data, options);
},
async readFile(path, options) {
options = getOptions(options, { flag: 'r' });
if (path instanceof FileHandle)
return readFileHandle(path, options);
const fd = await promises.open(path, options.flag, 0o666);
return readFileHandle(fd, options).finally(fd.close.bind(fd));
}
};
let warn = true;
// TODO(jasnell): Exposing this as a property with a getter works fine with
// commonjs but is going to be problematic for named imports support under
// ESM. A different approach will have to be followed there.
Object.defineProperty(fs, 'promises', {
configurable: true,
enumerable: true,
get() {
if (warn) {
warn = false;
process.emitWarning('The fs.promises API is experimental',
'ExperimentalWarning');
}
return promises;
}
});

507
lib/fs/promises.js Normal file
View File

@ -0,0 +1,507 @@
'use strict';
process.emitWarning('The fs/promises API is experimental',
'ExperimentalWarning');
const {
F_OK,
O_SYMLINK,
O_WRONLY,
S_IFMT,
S_IFREG
} = process.binding('constants').fs;
const binding = process.binding('fs');
const { Buffer, kMaxLength } = require('buffer');
const errors = require('internal/errors');
const { getPathFromURL } = require('internal/url');
const { isUint8Array } = require('internal/util/types');
const {
copyObject,
getOptions,
isUint32,
modeNum,
nullCheck,
preprocessSymlinkDestination,
statsFromValues,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
validateBuffer,
validateLen,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath,
validateUint32
} = require('internal/fs');
const pathModule = require('path');
const kHandle = Symbol('handle');
const { kUsePromises } = binding;
class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
}
getAsyncId() {
return this[kHandle].getAsyncId();
}
get fd() {
return this[kHandle].fd;
}
appendFile(data, options) {
return appendFile(this, data, options);
}
chmod(mode) {
return fchmod(this, mode);
}
chown(uid, gid) {
return fchown(this, uid, gid);
}
datasync() {
return fdatasync(this);
}
sync() {
return fsync(this);
}
read(buffer, offset, length, position) {
return read(this, buffer, offset, length, position);
}
readFile(options) {
return readFile(this, options);
}
stat() {
return fstat(this);
}
truncate(len = 0) {
return ftruncate(this, len);
}
utimes(atime, mtime) {
return futimes(this, atime, mtime);
}
write(buffer, offset, length, position) {
return write(this, buffer, offset, length, position);
}
writeFile(data, options) {
return writeFile(this, data, options);
}
close() {
return this[kHandle].close();
}
}
function validateFileHandle(handle) {
if (!(handle instanceof FileHandle))
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'filehandle', 'FileHandle');
}
async function writeFileHandle(filehandle, data, options) {
let buffer = isUint8Array(data) ?
data : Buffer.from('' + data, options.encoding || 'utf8');
let remaining = buffer.length;
if (remaining === 0) return;
do {
const { bytesWritten } =
await write(filehandle, buffer, 0,
Math.min(16384, buffer.length));
remaining -= bytesWritten;
buffer = buffer.slice(bytesWritten);
} while (remaining > 0);
}
async function readFileHandle(filehandle, options) {
const statFields = await binding.fstat(filehandle.fd, kUsePromises);
let size;
if ((statFields[1/*mode*/] & S_IFMT) === S_IFREG) {
size = statFields[8/*size*/];
} else {
size = 0;
}
if (size === 0)
return Buffer.alloc(0);
if (size > kMaxLength)
throw new errors.RangeError('ERR_BUFFER_TOO_LARGE');
const chunks = [];
const chunkSize = Math.min(size, 16384);
const buf = Buffer.alloc(chunkSize);
let totalRead = 0;
do {
const { bytesRead, buffer } =
await read(filehandle, buf, 0, buf.length);
totalRead = bytesRead;
if (totalRead > 0)
chunks.push(buffer.slice(0, totalRead));
} while (totalRead === chunkSize);
return Buffer.concat(chunks);
}
// All of the functions are defined as async in order to ensure that errors
// thrown cause promise rejections rather than being thrown synchronously.
async function access(path, mode = F_OK) {
path = getPathFromURL(path);
validatePath(path);
mode = mode | 0;
return binding.access(pathModule.toNamespacedPath(path), mode,
kUsePromises);
}
async function copyFile(src, dest, flags) {
src = getPathFromURL(src);
dest = getPathFromURL(dest);
validatePath(src, 'src');
validatePath(dest, 'dest');
flags = flags | 0;
return binding.copyFile(pathModule.toNamespacedPath(src),
pathModule.toNamespacedPath(dest),
flags, kUsePromises);
}
// Note that unlike fs.open() which uses numeric file descriptors,
// fsPromises.open() uses the fs.FileHandle class.
async function open(path, flags, mode) {
mode = modeNum(mode, 0o666);
path = getPathFromURL(path);
validatePath(path);
validateUint32(mode, 'mode');
return new FileHandle(
await binding.openFileHandle(pathModule.toNamespacedPath(path),
stringToFlags(flags),
mode, kUsePromises));
}
async function read(handle, buffer, offset, length, position) {
validateFileHandle(handle);
validateBuffer(buffer);
offset |= 0;
length |= 0;
if (length === 0)
return { bytesRead: length, buffer };
validateOffsetLengthRead(offset, length, buffer.length);
if (!isUint32(position))
position = -1;
const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
position, kUsePromises)) || 0;
return { bytesRead, buffer };
}
async function write(handle, buffer, offset, length, position) {
validateFileHandle(handle);
if (buffer.length === 0)
return { bytesWritten: 0, buffer };
if (isUint8Array(buffer)) {
if (typeof offset !== 'number')
offset = 0;
if (typeof length !== 'number')
length = buffer.length - offset;
if (typeof position !== 'number')
position = null;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
const bytesWritten =
(await binding.writeBuffer(handle.fd, buffer, offset,
length, position, kUsePromises)) || 0;
return { bytesWritten, buffer };
}
if (typeof buffer !== 'string')
buffer += '';
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
length, kUsePromises)) || 0;
return { bytesWritten, buffer };
}
async function rename(oldPath, newPath) {
oldPath = getPathFromURL(oldPath);
newPath = getPathFromURL(newPath);
validatePath(oldPath, 'oldPath');
validatePath(newPath, 'newPath');
return binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
}
async function truncate(path, len = 0) {
return ftruncate(await open(path, 'r+'), len);
}
async function ftruncate(handle, len = 0) {
validateFileHandle(handle);
validateLen(len);
len = Math.max(0, len);
return binding.ftruncate(handle.fd, len, kUsePromises);
}
async function rmdir(path) {
path = getPathFromURL(path);
validatePath(path);
return binding.rmdir(pathModule.toNamespacedPath(path), kUsePromises);
}
async function fdatasync(handle) {
validateFileHandle(handle);
return binding.fdatasync(handle.fd, kUsePromises);
}
async function fsync(handle) {
validateFileHandle(handle);
return binding.fsync(handle.fd, kUsePromises);
}
async function mkdir(path, mode) {
mode = modeNum(mode, 0o777);
path = getPathFromURL(path);
validatePath(path);
validateUint32(mode, 'mode');
return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises);
}
async function readdir(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
return binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
}
async function readlink(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path, 'oldPath');
return binding.readlink(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
}
async function symlink(target, path, type_) {
const type = (typeof type_ === 'string' ? type_ : null);
target = getPathFromURL(target);
path = getPathFromURL(path);
validatePath(target, 'target');
validatePath(path);
return binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path),
stringToSymlinkType(type),
kUsePromises);
}
async function fstat(handle) {
validateFileHandle(handle);
return statsFromValues(await binding.fstat(handle.fd, kUsePromises));
}
async function lstat(path) {
path = getPathFromURL(path);
validatePath(path);
return statsFromValues(
await binding.lstat(pathModule.toNamespacedPath(path), kUsePromises));
}
async function stat(path) {
path = getPathFromURL(path);
validatePath(path);
return statsFromValues(
await binding.stat(pathModule.toNamespacedPath(path), kUsePromises));
}
async function link(existingPath, newPath) {
existingPath = getPathFromURL(existingPath);
newPath = getPathFromURL(newPath);
validatePath(existingPath, 'existingPath');
validatePath(newPath, 'newPath');
return binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
kUsePromises);
}
async function unlink(path) {
path = getPathFromURL(path);
validatePath(path);
return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
}
async function fchmod(handle, mode) {
mode = modeNum(mode);
validateFileHandle(handle);
validateUint32(mode, 'mode');
if (mode < 0 || mode > 0o777)
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'mode');
return binding.fchmod(handle.fd, mode, kUsePromises);
}
async function chmod(path, mode) {
path = getPathFromURL(path);
validatePath(path);
mode = modeNum(mode);
validateUint32(mode, 'mode');
return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
}
async function lchmod(path, mode) {
if (O_SYMLINK !== undefined) {
const fd = await open(path,
O_WRONLY | O_SYMLINK);
return fchmod(fd, mode).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
}
async function lchown(path, uid, gid) {
if (O_SYMLINK !== undefined) {
const fd = await open(path,
O_WRONLY | O_SYMLINK);
return fchmod(fd, uid, gid).finally(fd.close.bind(fd));
}
throw new errors.Error('ERR_METHOD_NOT_IMPLEMENTED');
}
async function fchown(handle, uid, gid) {
validateFileHandle(handle);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.fchown(handle.fd, uid, gid, kUsePromises);
}
async function chown(path, uid, gid) {
path = getPathFromURL(path);
validatePath(path);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
return binding.chown(pathModule.toNamespacedPath(path),
uid, gid, kUsePromises);
}
async function utimes(path, atime, mtime) {
path = getPathFromURL(path);
validatePath(path);
return binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
kUsePromises);
}
async function futimes(handle, atime, mtime) {
validateFileHandle(handle);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
return binding.futimes(handle.fd, atime, mtime, kUsePromises);
}
async function realpath(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
return binding.realpath(path, options.encoding, kUsePromises);
}
async function mkdtemp(prefix, options) {
options = getOptions(options, {});
if (!prefix || typeof prefix !== 'string') {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
'prefix',
'string',
prefix);
}
nullCheck(prefix);
return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
}
async function writeFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
const flag = options.flag || 'w';
if (path instanceof FileHandle)
return writeFileHandle(path, data, options);
const fd = await open(path, flag, options.mode);
return writeFileHandle(fd, data, options).finally(fd.close.bind(fd));
}
async function appendFile(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
options = copyObject(options);
options.flag = options.flag || 'a';
return writeFile(path, data, options);
}
async function readFile(path, options) {
options = getOptions(options, { flag: 'r' });
if (path instanceof FileHandle)
return readFileHandle(path, options);
const fd = await open(path, options.flag, 0o666);
return readFileHandle(fd, options).finally(fd.close.bind(fd));
}
module.exports = {
access,
copyFile,
open,
read,
write,
rename,
truncate,
ftruncate,
rmdir,
fdatasync,
fsync,
mkdir,
readdir,
readlink,
symlink,
fstat,
lstat,
stat,
link,
unlink,
fchmod,
chmod,
lchmod,
lchown,
fchown,
chown,
utimes,
futimes,
realpath,
mkdtemp,
writeFile,
appendFile,
readFile
};

View File

@ -39,6 +39,7 @@
'lib/domain.js',
'lib/events.js',
'lib/fs.js',
'lib/fs/promises.js',
'lib/http.js',
'lib/http2.js',
'lib/_http_agent.js',

View File

@ -2,6 +2,7 @@
const common = require('../common');
const fs = require('fs');
const fsPromises = require('fs/promises');
const path = require('path');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
@ -16,20 +17,20 @@ const buffer = Buffer.from('abc'.repeat(1000));
const buffer2 = Buffer.from('xyz'.repeat(1000));
async function doWrite() {
await fs.promises.writeFile(dest, buffer);
await fsPromises.writeFile(dest, buffer);
const data = fs.readFileSync(dest);
assert.deepStrictEqual(data, buffer);
}
async function doAppend() {
await fs.promises.appendFile(dest, buffer2);
await fsPromises.appendFile(dest, buffer2);
const data = fs.readFileSync(dest);
const buf = Buffer.concat([buffer, buffer2]);
assert.deepStrictEqual(buf, data);
}
async function doRead() {
const data = await fs.promises.readFile(dest);
const data = await fsPromises.readFile(dest);
const buf = fs.readFileSync(dest);
assert.deepStrictEqual(buf, data);
}

View File

@ -5,7 +5,7 @@ const assert = require('assert');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs/promises');
const {
access,
chmod,
@ -32,7 +32,7 @@ const {
write,
unlink,
utimes
} = fs.promises;
} = fsPromises;
const tmpDir = tmpdir.path;

View File

@ -3,6 +3,7 @@
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const fsPromises = require('fs/promises');
const net = require('net');
const providers = Object.assign({}, process.binding('async_wrap').Providers);
const fixtures = require('../common/fixtures');
@ -171,7 +172,7 @@ if (common.hasCrypto) { // eslint-disable-line crypto-check
{
async function openTest() {
const fd = await fs.promises.open(__filename, 'r');
const fd = await fsPromises.open(__filename, 'r');
testInitialized(fd, 'FileHandle');
await fd.close();
}