1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

fix(node/fs/promises): watch should be async iterable (#24805)

The way `fs.watch` works is different in `node:fs/promises` than
`node:fs`. It has a different function signature and it returns an async
iterable instead, see
https://nodejs.org/api/fs.html#fspromiseswatchfilename-options

Fixes https://github.com/denoland/deno/issues/24661
This commit is contained in:
Marvin Hagemeister 2024-07-31 13:07:49 +02:00 committed by GitHub
parent 1e2581e57b
commit 9e6288ec61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 17 deletions

View file

@ -155,22 +155,43 @@ export function watch(
return fsWatcher; return fsWatcher;
} }
export const watchPromise = promisify(watch) as ( export function watchPromise(
& (( filename: string | Buffer | URL,
filename: string | URL, options?: {
options: watchOptions, persistent?: boolean;
listener: watchListener, recursive?: boolean;
) => Promise<FSWatcher>) encoding?: string;
& (( signal?: AbortSignal;
filename: string | URL, },
listener: watchListener, ): AsyncIterable<{ eventType: string; filename: string | Buffer | null }> {
) => Promise<FSWatcher>) const watchPath = getValidatedPath(filename).toString();
& ((
filename: string | URL, const watcher = Deno.watchFs(watchPath, {
options: watchOptions, recursive: options?.recursive ?? false,
) => Promise<FSWatcher>) });
& ((filename: string | URL) => Promise<FSWatcher>)
); if (options?.signal) {
options?.signal.addEventListener("abort", () => watcher.close());
}
const fsIterable = watcher[Symbol.asyncIterator]();
const iterable = {
async next() {
const result = await fsIterable.next();
if (result.done) return result;
const eventType = convertDenoFsEventToNodeFsEvent(result.value.kind);
return {
value: { eventType, filename: basename(result.value.paths[0]) },
done: result.done,
};
},
};
return {
[Symbol.asyncIterator]: () => iterable,
};
}
type WatchFileListener = (curr: Stats, prev: Stats) => void; type WatchFileListener = (curr: Stats, prev: Stats) => void;
type WatchFileOptions = { type WatchFileOptions = {

View file

@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { unwatchFile, watch, watchFile } from "node:fs"; import { unwatchFile, watch, watchFile } from "node:fs";
import { assertEquals } from "@std/assert"; import { watch as watchPromise } from "node:fs/promises";
import { assert, assertEquals } from "@std/assert";
function wait(time: number) { function wait(time: number) {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -52,3 +53,46 @@ Deno.test({
watcher.unref(); watcher.unref();
}, },
}); });
Deno.test({
name: "node [fs/promises] watch should return async iterable",
sanitizeOps: false,
sanitizeResources: false,
async fn() {
const file = Deno.makeTempFileSync();
Deno.writeTextFileSync(file, "foo");
const result: { eventType: string; filename: string | null }[] = [];
const controller = new AbortController();
const watcher = watchPromise(file, {
// Node types resolved by the LSP clash with ours
// deno-lint-ignore no-explicit-any
signal: controller.signal as any,
});
const deferred = Promise.withResolvers<void>();
let stopLength = 0;
setTimeout(async () => {
Deno.writeTextFileSync(file, "something");
controller.abort();
stopLength = result.length;
await wait(100);
Deno.writeTextFileSync(file, "something else");
await wait(100);
deferred.resolve();
}, 100);
for await (const event of watcher) {
result.push(event);
}
await deferred.promise;
assertEquals(result.length, stopLength);
assert(
result.every((item) =>
typeof item.eventType === "string" && typeof item.filename === "string"
),
);
},
});