From ed448b84c6c8806fc1f94e0d3c489adc2d564b34 Mon Sep 17 00:00:00 2001 From: ianshade Date: Thu, 5 May 2022 16:49:54 +0200 Subject: [PATCH 1/4] wip: use async versions of fs functions --- lib/is.js | 43 ++++++++++++-------- lib/watch.d.ts | 14 +++---- lib/watch.js | 105 +++++++++++++++++++++++++++++-------------------- package.json | 4 +- 4 files changed, 96 insertions(+), 70 deletions(-) diff --git a/lib/is.js b/lib/is.js index ebe0600..38ff721 100644 --- a/lib/is.js +++ b/lib/is.js @@ -7,10 +7,8 @@ function matchObject(item, str) { === '[object ' + str + ']'; } -function checkStat(name, fn) { - try { - return fn(name); - } catch (err) { +function checkStat(err) { + if (err) { if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) { if (err.code !== 'ENOENT') { console.warn('Warning: Cannot access %s', name); @@ -19,6 +17,7 @@ function checkStat(name, fn) { } throw err; } + return true; } var is = { @@ -49,26 +48,36 @@ var is = { number: function(item) { return matchObject(item, 'Number'); }, - exists: function(name) { - return fs.existsSync(name); + exists: async function(name) { + return new Promise(resolve => { + fs.access(name, fs.constants.F_OK, (err) => { + resolve(!err) + }) + }) }, - file: function(name) { - return checkStat(name, function(n) { - return fs.statSync(n).isFile() - }); + file: async function(name) { + return new Promise((resolve) => { + return fs.stat(name, (err, stats) => { + resolve(checkStat(err) && stats.isFile()) + }) + }) }, samePath: function(a, b) { return path.resolve(a) === path.resolve(b); }, - directory: function(name) { - return checkStat(name, function(n) { - return fs.statSync(n).isDirectory() - }); + directory: async function(name) { + return new Promise((resolve) => { + return fs.stat(name, (err, stats) => { + resolve(checkStat(err) && stats.isDirectory()) + }) + }) }, symbolicLink: function(name) { - return checkStat(name, function(n) { - return fs.lstatSync(n).isSymbolicLink(); - }); + return new Promise((resolve) => { + return fs.lstat(name, (err, stats) => { + resolve(checkStat(err) && stats.isSymbolicLink()) + }) + }) }, windows: function() { return os.platform() === 'win32'; diff --git a/lib/watch.d.ts b/lib/watch.d.ts index 9eca5d4..9bcfd6d 100644 --- a/lib/watch.d.ts +++ b/lib/watch.d.ts @@ -16,12 +16,12 @@ import { FSWatcher } from 'fs'; * @param {Options|string} options * @param {Function} callback */ -declare function watch(pathName: PathName): Watcher; -declare function watch(pathName: PathName, options: Options) : Watcher; -declare function watch(pathName: PathName, callback: Callback): Watcher; -declare function watch(pathName: PathName, options: Options, callback: Callback): Watcher; +export function watch(pathName: PathName): Watcher; +export function watch(pathName: PathName, options: Options) : Watcher; +export function watch(pathName: PathName, callback: Callback): Watcher; +export function watch(pathName: PathName, options: Options, callback: Callback): Watcher; -type EventType = 'update' | 'remove'; +export type EventType = 'update' | 'remove'; type Callback = (eventType: EventType, filePath: string) => any; type PathName = string | Array; type FilterReturn = boolean | symbol; @@ -60,7 +60,7 @@ type Options = { delay ?: number; }; -declare interface Watcher extends FSWatcher { +export interface Watcher extends FSWatcher { /** * Returns `true` if the watcher has been closed. */ @@ -71,5 +71,3 @@ declare interface Watcher extends FSWatcher { */ getWatchedPaths(): Array; } - -export default watch; diff --git a/lib/watch.js b/lib/watch.js index 95bfeb5..6721a1e 100644 --- a/lib/watch.js +++ b/lib/watch.js @@ -52,15 +52,17 @@ function guard(fn) { } } -function composeMessage(names) { - return names.map(function(n) { - return is.exists(n) +async function composeMessage(names) { + const existing = names.map((n) => is.exists(n)) + await Promise.all(existing) + return names.map((n, index) => { + return existing[index] ? [EVENT_UPDATE, n] : [EVENT_REMOVE, n]; - }); + }) } -function getMessages(cache) { +async function getMessages(cache) { var filtered = unique(cache); // Saving file from an editor? If so, assuming the @@ -76,9 +78,13 @@ function getMessages(cache) { return c.replace(reg, ''); })); if (dup) { - filtered = filtered.filter(function(m) { + var existing = filtered.map(function(m) { return is.exists(m); }); + await Promise.all(existing); + filtered = filtered.filter((v, index) => { + return existing[index] + }) } } @@ -92,16 +98,18 @@ function debounce(info, fn) { if (!is.number(delay)) { delay = 200; } - function handle() { - getMessages(cache).forEach(function(msg) { + async function handle() { + const cacheClone = [...cache] + cache = []; + const messages = await getMessages(cacheClone) + messages.forEach(function(msg) { msg[1] = Buffer.from(msg[1]); if (encoding !== 'buffer') { msg[1] = msg[1].toString(encoding); } fn.apply(null, msg); }); - timer = null; - cache = []; + timer = cache.length ? setTimeout(handle, delay) : null; } return function(rawEvt, name) { cache.push(name); @@ -126,8 +134,8 @@ function createDupsFilter() { } } -function getSubDirectories(dir, fn, done = function() {}) { - if (is.directory(dir)) { +async function getSubDirectories(dir, fn, done = function() {}) { + if (await (is.directory(dir))) { fs.readdir(dir, function(err, all) { if (err) { // don't throw permission errors. @@ -136,11 +144,12 @@ function getSubDirectories(dir, fn, done = function() {}) { } else { throw err; } - } - else { - all.forEach(function(f) { + } else { + all.forEach(async function(f) { var sdir = path.join(dir, f); - if (is.directory(sdir)) fn(sdir); + if (await (is.directory(sdir))) { + fn(sdir); + } }); done(); } @@ -292,19 +301,20 @@ Watcher.prototype.add = function(watcher, info) { if (!has) { var fullPath = path.resolve(name); // remove watcher on removal - if (!is.exists(name)) { - self.close(fullPath); - } - // watch new created directory - else { - var shouldWatch = is.directory(name) + is.exists(name).then(async exists => { + if (!exists) { + self.close(fullPath); + } else { + // watch new created directory + var shouldWatch = await is.directory(name) && !self.watchers[fullPath] && shouldNotSkip(name, info.options.filter); - if (shouldWatch) { - self.watchDirectory(name, info.options); + if (shouldWatch) { + self.watchDirectory(name, info.options); + } } - } + }) } }); } @@ -470,12 +480,16 @@ function watch(fpath, options, fn) { fpath = fpath.toString(); } - if (!is.array(fpath) && !is.exists(fpath)) { - process.nextTick(function() { - watcher.emit('error', - new Error(fpath + ' does not exist.') - ); - }); + if (!is.array(fpath)) { + is.exists(fpath).then((result) => { + if (!result) { + process.nextTick(function() { + watcher.emit('error', + new Error(fpath + ' does not exist.') + ); + }); + } + }) } if (is.string(options)) { @@ -513,20 +527,25 @@ function watch(fpath, options, fn) { })); } - if (is.file(fpath)) { - watcher.watchFile(fpath, options, fn); - emitReady(watcher); - } - - else if (is.directory(fpath)) { - var counter = semaphore(function () { + is.file(fpath).then((isFile) => { + if (isFile) { + watcher.watchFile(fpath, options, fn); emitReady(watcher); - }); - watcher.watchDirectory(fpath, options, fn, counter); - } + } else { + is.directory(fpath).then((isDirectory) => { + if (!isDirectory) return + var counter = semaphore(function () { + emitReady(watcher); + }); + watcher.watchDirectory(fpath, options, fn, counter); + }) + } + }) + return watcher.expose(); } -module.exports = watch; -module.exports.default = watch; +module.exports = { + watch +} diff --git a/package.json b/package.json index 241dc81..e57e55c 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ }, "url": "https://github.com/yuanchuan/node-watch", "author": "yuanchuan (http://yuanchuan.name)", - "main": "./lib/watch", - "types": "./lib/watch.d.ts", + "main": "lib/watch.js", + "types": "lib/watch.d.ts", "files": [ "lib/" ], From d7f6febd9dcb4797d81223d8d8d1457d4622546a Mon Sep 17 00:00:00 2001 From: ianshade Date: Fri, 6 May 2022 11:23:29 +0200 Subject: [PATCH 2/4] fix: existence check in composeMessage --- lib/watch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/watch.js b/lib/watch.js index 6721a1e..4a69a1c 100644 --- a/lib/watch.js +++ b/lib/watch.js @@ -53,10 +53,10 @@ function guard(fn) { } async function composeMessage(names) { - const existing = names.map((n) => is.exists(n)) - await Promise.all(existing) + const ps = names.map((n) => is.exists(n)) + const exists = await Promise.all(ps) return names.map((n, index) => { - return existing[index] + return exists[index] ? [EVENT_UPDATE, n] : [EVENT_REMOVE, n]; }) From b32cdaf43a1c6a5b2f15b7b116dc63704a736991 Mon Sep 17 00:00:00 2001 From: ianshade Date: Mon, 9 May 2022 10:40:36 +0200 Subject: [PATCH 3/4] chore: revert unnecessary export and package.json changes --- lib/watch.d.ts | 10 ++++++---- lib/watch.js | 5 ++--- package.json | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/watch.d.ts b/lib/watch.d.ts index 9bcfd6d..848e53a 100644 --- a/lib/watch.d.ts +++ b/lib/watch.d.ts @@ -16,10 +16,10 @@ import { FSWatcher } from 'fs'; * @param {Options|string} options * @param {Function} callback */ -export function watch(pathName: PathName): Watcher; -export function watch(pathName: PathName, options: Options) : Watcher; -export function watch(pathName: PathName, callback: Callback): Watcher; -export function watch(pathName: PathName, options: Options, callback: Callback): Watcher; +declare function watch(pathName: PathName): Watcher; +declare function watch(pathName: PathName, options: Options) : Watcher; +declare function watch(pathName: PathName, callback: Callback): Watcher; +declare function watch(pathName: PathName, options: Options, callback: Callback): Watcher; export type EventType = 'update' | 'remove'; type Callback = (eventType: EventType, filePath: string) => any; @@ -71,3 +71,5 @@ export interface Watcher extends FSWatcher { */ getWatchedPaths(): Array; } + +export default watch; diff --git a/lib/watch.js b/lib/watch.js index 4a69a1c..fca3780 100644 --- a/lib/watch.js +++ b/lib/watch.js @@ -546,6 +546,5 @@ function watch(fpath, options, fn) { return watcher.expose(); } -module.exports = { - watch -} +module.exports = watch; +module.exports.default = watch; diff --git a/package.json b/package.json index e57e55c..241dc81 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ }, "url": "https://github.com/yuanchuan/node-watch", "author": "yuanchuan (http://yuanchuan.name)", - "main": "lib/watch.js", - "types": "lib/watch.d.ts", + "main": "./lib/watch", + "types": "./lib/watch.d.ts", "files": [ "lib/" ], From e472d2efe5dd758126a97a01d7de1e2644a82ce1 Mon Sep 17 00:00:00 2001 From: ianshade Date: Tue, 10 May 2022 14:39:34 +0200 Subject: [PATCH 4/4] fix: promise handling --- lib/is.js | 46 +++++++++------------- lib/watch.js | 108 ++++++++++++++++++++++++++------------------------- package.json | 4 +- 3 files changed, 76 insertions(+), 82 deletions(-) diff --git a/lib/is.js b/lib/is.js index 38ff721..0aa34a8 100644 --- a/lib/is.js +++ b/lib/is.js @@ -1,4 +1,4 @@ -var fs = require('fs'); +var fs = require('fs-extra'); var path = require('path'); var os = require('os'); @@ -8,16 +8,14 @@ function matchObject(item, str) { } function checkStat(err) { - if (err) { - if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) { - if (err.code !== 'ENOENT') { - console.warn('Warning: Cannot access %s', name); - } - return false; + if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) { + if (err.code !== 'ENOENT') { + console.warn('Warning: Cannot access %s', name); } + return false; + } else { throw err; } - return true; } var is = { @@ -49,35 +47,27 @@ var is = { return matchObject(item, 'Number'); }, exists: async function(name) { - return new Promise(resolve => { - fs.access(name, fs.constants.F_OK, (err) => { - resolve(!err) - }) - }) + return fs.access(name, fs.constants.F_OK) + .then(() => true) + .catch(() => false) }, file: async function(name) { - return new Promise((resolve) => { - return fs.stat(name, (err, stats) => { - resolve(checkStat(err) && stats.isFile()) - }) - }) + return fs.stat(name) + .then((stats) => stats.isFile()) + .catch(checkStat) }, samePath: function(a, b) { return path.resolve(a) === path.resolve(b); }, directory: async function(name) { - return new Promise((resolve) => { - return fs.stat(name, (err, stats) => { - resolve(checkStat(err) && stats.isDirectory()) - }) - }) + return fs.stat(name) + .then((stats) => stats.isDirectory()) + .catch(checkStat) }, symbolicLink: function(name) { - return new Promise((resolve) => { - return fs.lstat(name, (err, stats) => { - resolve(checkStat(err) && stats.isSymbolicLink()) - }) - }) + return fs.stat(name) + .then((stats) => stats.isSymbolicLink()) + .catch(checkStat) }, windows: function() { return os.platform() === 'win32'; diff --git a/lib/watch.js b/lib/watch.js index fca3780..66959ab 100644 --- a/lib/watch.js +++ b/lib/watch.js @@ -1,4 +1,4 @@ -var fs = require('fs'); +var fs = require('fs-extra'); var path = require('path'); var util = require('util'); var events = require('events'); @@ -101,6 +101,7 @@ function debounce(info, fn) { async function handle() { const cacheClone = [...cache] cache = []; + timer = null; const messages = await getMessages(cacheClone) messages.forEach(function(msg) { msg[1] = Buffer.from(msg[1]); @@ -109,7 +110,6 @@ function debounce(info, fn) { } fn.apply(null, msg); }); - timer = cache.length ? setTimeout(handle, delay) : null; } return function(rawEvt, name) { cache.push(name); @@ -134,28 +134,24 @@ function createDupsFilter() { } } -async function getSubDirectories(dir, fn, done = function() {}) { - if (await (is.directory(dir))) { - fs.readdir(dir, function(err, all) { - if (err) { - // don't throw permission errors. - if (/^(EPERM|EACCES)$/.test(err.code)) { - console.warn('Warning: Cannot access %s.', dir); - } else { - throw err; - } - } else { - all.forEach(async function(f) { - var sdir = path.join(dir, f); - if (await (is.directory(sdir))) { - fn(sdir); - } - }); - done(); +async function getSubDirectories(dir, fn) { + if (!(await is.directory(dir))) return; + try { + const all = await fs.readdir(dir); + const subDirs = all.map(async function(f) { + var sdir = path.join(dir, f); + if (await (is.directory(sdir))) { + fn(sdir); } }); - } else { - done(); + return Promise.allSettled(subDirs); + } catch(err) { + // don't throw permission errors. + if (/^(EPERM|EACCES)$/.test(err.code)) { + console.warn('Warning: Cannot access %s.', dir); + } else { + throw err; + } } } @@ -227,7 +223,8 @@ Watcher.prototype.close = function(fullPath) { } getSubDirectories(fullPath, function(fpath) { self.close(fpath); - }); + }) + .catch((err) => self.emit('error', err)) } else { Object.keys(self.watchers).forEach(function(fpath) { @@ -300,21 +297,23 @@ Watcher.prototype.add = function(watcher, info) { hasNativeRecursive(function(has) { if (!has) { var fullPath = path.resolve(name); - // remove watcher on removal - is.exists(name).then(async exists => { - if (!exists) { - self.close(fullPath); - } else { - // watch new created directory - var shouldWatch = await is.directory(name) - && !self.watchers[fullPath] - && shouldNotSkip(name, info.options.filter); - - if (shouldWatch) { - self.watchDirectory(name, info.options); + is.exists(name) + .then(async exists => { + if (!exists) { + // remove watcher on removal + self.close(fullPath); + } else { + // watch new created directory + var shouldWatch = !self.watchers[fullPath] + && await is.directory(name) + && shouldNotSkip(name, info.options.filter); + + if (shouldWatch) { + self.watchDirectory(name, info.options); + } } - } - }) + }) + .catch((err) => self.emit('error', err)) } }); } @@ -423,7 +422,9 @@ Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCoun if (shouldNotSkip(d, options.filter)) { self.watchDirectory(d, options, null, counter); } - }, counter()); + }) + .then(() => counter()) + .catch((err) => self.emit('error', err)) } done(); @@ -527,21 +528,22 @@ function watch(fpath, options, fn) { })); } - is.file(fpath).then((isFile) => { - if (isFile) { - watcher.watchFile(fpath, options, fn); - emitReady(watcher); - } else { - is.directory(fpath).then((isDirectory) => { - if (!isDirectory) return - var counter = semaphore(function () { - emitReady(watcher); - }); - watcher.watchDirectory(fpath, options, fn, counter); - }) - } - }) - + is.file(fpath) + .then((isFile) => { + if (isFile) { + watcher.watchFile(fpath, options, fn); + emitReady(watcher); + } else { + return is.directory(fpath).then((isDirectory) => { + if (!isDirectory) return + var counter = semaphore(function () { + emitReady(watcher); + }); + watcher.watchDirectory(fpath, options, fn, counter); + }) + } + }) + .catch((err) => self.emit('error', err)) return watcher.expose(); } diff --git a/package.json b/package.json index 241dc81..4363875 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "engines": { "node": ">=6" }, + "dependencies": { + "fs-extra": "^10.1.0" + }, "devDependencies": { - "fs-extra": "^7.0.1", "mocha": "^5.2.0" } }