Skip to content

promises.watch #1120

@streamich

Description

@streamich

Add memfs implementation of promises.watch and promises.watchFile.

Node.js documentation for context:


fsPromises.watch(filename[, options])#

Added in: v15.9.0, v14.18.0
  • filename <string> | <Buffer> | <URL>
  • options <string> | <Object>
    • persistent <boolean> Indicates whether the process should continue to run as long as files are being watched. Default: true.
    • recursive <boolean> Indicates whether all subdirectories should be watched, or only the current directory. This applies when a directory is specified, and only on supported platforms (See caveats). Default: false.
    • encoding <string> Specifies the character encoding to be used for the filename passed to the listener. Default: 'utf8'.
    • signal <AbortSignal> An <AbortSignal> used to signal when the watcher should stop.
    • maxQueue <number> Specifies the number of events to queue between iterations of the <AsyncIterator> returned. Default: 2048.
    • overflow <string> Either 'ignore' or 'throw' when there are more events to be queued than maxQueue allows. 'ignore' means overflow events are dropped and a warning is emitted, while 'throw' means to throw an exception. Default: 'ignore'.
  • Returns: <AsyncIterator> of objects with the properties:

Returns an async iterator that watches for changes on filename, where filename is either a file or a directory.

const { watch } = require('node:fs/promises');

const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 10000);

(async () => {
try {
const watcher = watch(__filename, { signal });
for await (const event of watcher)
console.log(event);
} catch (err) {
if (err.name === 'AbortError')
return;
throw err;
}
})();
copy

On most platforms, 'rename' is emitted whenever a filename appears or disappears in the directory.

All the caveats for fs.watch() also apply to fsPromises.watch().

fsPromises.writeFile(file, data[, options])#

History

   
   
   
   
   
   

Asynchronously writes data to a file, replacing the file if it already exists. data can be a string, a buffer, an <AsyncIterable>, or an <Iterable> object.

The encoding option is ignored if data is a buffer.

If options is a string, then it specifies the encoding.

The mode option only affects the newly created file. See fs.open() for more details.

Any specified <FileHandle> has to support writing.

It is unsafe to use fsPromises.writeFile() multiple times on the same file without waiting for the promise to be settled.

Similarly to fsPromises.readFile - fsPromises.writeFile is a convenience method that performs multiple write calls internally to write the buffer passed to it. For performance sensitive code consider using fs.createWriteStream() or filehandle.createWriteStream().

It is possible to use an <AbortSignal> to cancel an fsPromises.writeFile(). Cancelation is "best effort", and some amount of data is likely still to be written.

import { writeFile } from 'node:fs/promises';
import { Buffer } from 'node:buffer';

try {
const controller = new AbortController();
const { signal } = controller;
const data = new Uint8Array(Buffer.from('Hello Node.js'));
const promise = writeFile('message.txt', data, { signal });

// Abort the request before the promise settles.
controller.abort();

await promise;
} catch (err) {
// When a request is aborted - err is an AbortError
console.error(err);
}
copy

Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering fs.writeFile performs.

fsPromises.watch(filename[, options])#
Added in: v15.9.0, v14.18.0
filename | |
options |
persistent
Indicates whether the process should continue to run as long as files are being watched. Default: true.
recursive Indicates whether all subdirectories should be watched, or only the current directory. This applies when a directory is specified, and only on supported platforms (See caveats). Default: false.
encoding Specifies the character encoding to be used for the filename passed to the listener. Default: 'utf8'.
signal An used to signal when the watcher should stop.
maxQueue Specifies the number of events to queue between iterations of the returned. Default: 2048.
overflow Either 'ignore' or 'throw' when there are more events to be queued than maxQueue allows. 'ignore' means overflow events are dropped and a warning is emitted, while 'throw' means to throw an exception. Default: 'ignore'.
Returns: of objects with the properties:
eventType The type of change
filename | | The name of the file changed.
Returns an async iterator that watches for changes on filename, where filename is either a file or a directory.

const { watch } = require('node:fs/promises');

const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 10000);

(async () => {
try {
const watcher = watch(__filename, { signal });
for await (const event of watcher)
console.log(event);
} catch (err) {
if (err.name === 'AbortError')
return;
throw err;
}
})(); copy
On most platforms, 'rename' is emitted whenever a filename appears or disappears in the directory.

All the caveats for fs.watch() also apply to fsPromises.watch().

fsPromises.writeFile(file, data[, options])#
History
file | | | filename or FileHandle
data | | | | | |
options |
encoding | Default: 'utf8'
mode Default: 0o666
flag See support of file system flags. Default: 'w'.
flush If all data is successfully written to the file, and flush is true, filehandle.sync() is used to flush the data. Default: false.
signal allows aborting an in-progress writeFile
Returns: Fulfills with undefined upon success.
Asynchronously writes data to a file, replacing the file if it already exists. data can be a string, a buffer, an , or an object.

The encoding option is ignored if data is a buffer.

If options is a string, then it specifies the encoding.

The mode option only affects the newly created file. See fs.open() for more details.

Any specified has to support writing.

It is unsafe to use fsPromises.writeFile() multiple times on the same file without waiting for the promise to be settled.

Similarly to fsPromises.readFile - fsPromises.writeFile is a convenience method that performs multiple write calls internally to write the buffer passed to it. For performance sensitive code consider using fs.createWriteStream() or filehandle.createWriteStream().

It is possible to use an to cancel an fsPromises.writeFile(). Cancelation is "best effort", and some amount of data is likely still to be written.

import { writeFile } from 'node:fs/promises';
import { Buffer } from 'node:buffer';

try {
const controller = new AbortController();
const { signal } = controller;
const data = new Uint8Array(Buffer.from('Hello Node.js'));
const promise = writeFile('message.txt', data, { signal });

// Abort the request before the promise settles.
controller.abort();

await promise;
} catch (err) {
// When a request is aborted - err is an AbortError
console.error(err);
} copy
Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering fs.writeFile performs.


See Node.js watchers implementation for reference:

async function* watch(filename, options = kEmptyObject) {
  const path = toNamespacedPath(getValidatedPath(filename));
  validateObject(options, 'options');

  const {
    persistent = true,
    recursive = false,
    encoding = 'utf8',
    maxQueue = 2048,
    overflow = 'ignore',
    signal,
  } = options;

  validateBoolean(persistent, 'options.persistent');
  validateBoolean(recursive, 'options.recursive');
  validateInteger(maxQueue, 'options.maxQueue');
  validateOneOf(overflow, 'options.overflow', ['ignore', 'error']);
  validateAbortSignal(signal, 'options.signal');

  if (encoding && !isEncoding(encoding)) {
    const reason = 'is invalid encoding';
    throw new ERR_INVALID_ARG_VALUE('encoding', encoding, reason);
  }

  if (signal?.aborted)
    throw new AbortError(undefined, { cause: signal.reason });

  const handle = new FSEvent();
  let { promise, resolve } = PromiseWithResolvers();
  const queue = [];
  const oncancel = () => {
    handle.close();
    resolve();
  };

  try {
    if (signal) {
      kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
      signal.addEventListener('abort', oncancel, { __proto__: null, once: true, [kResistStopPropagation]: true });
    }
    handle.onchange = (status, eventType, filename) => {
      if (status < 0) {
        const error = new UVException({
          errno: status,
          syscall: 'watch',
          path: filename,
        });
        error.filename = filename;
        handle.close();
        ArrayPrototypePush(queue, error);
        resolve();
        return;
      }
      if (queue.length < maxQueue) {
        ArrayPrototypePush(queue, { __proto__: null, eventType, filename });
        resolve();
      } else if (overflow === 'error') {
        queue.length = 0;
        ArrayPrototypePush(queue, new ERR_FS_WATCH_QUEUE_OVERFLOW(maxQueue));
        resolve();
      } else {
        process.emitWarning('fs.watch maxQueue exceeded');
      }
    };

    const err = handle.start(path, persistent, recursive, encoding);
    if (err) {
      const error = new UVException({
        errno: err,
        syscall: 'watch',
        path: filename,
        message: err === UV_ENOSPC ?
          'System limit for number of file watchers reached' : '',
      });
      error.filename = filename;
      handle.close();
      throw error;
    }

    while (!signal?.aborted) {
      await promise;
      while (queue.length) {
        const item = ArrayPrototypeShift(queue);
        if (item instanceof Error) {
          throw item;
        } else {
          yield item;
        }
      }
      ({ promise, resolve } = PromiseWithResolvers());
    }
    if (signal?.aborted) {
      throw new AbortError(undefined, { cause: signal?.reason });
    }
  } finally {
    handle.close();
    signal?.removeEventListener('abort', oncancel);
  }
}

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions