From d5c5c20d18a5fe1b7c3161205c567ec2aab9b6b7 Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Mon, 4 Mar 2024 20:44:06 +0100 Subject: [PATCH] watch: debounce restart in watch mode --- lib/internal/main/watch_mode.js | 49 ++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 3be329c57af6db..6430343613e31d 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -6,6 +6,7 @@ const { ArrayPrototypeMap, ArrayPrototypePushApply, ArrayPrototypeSlice, + PromiseResolve, StringPrototypeStartsWith, } = primordials; @@ -110,16 +111,62 @@ async function restart() { start(); } +function debounce(fn, delay) { + let state = 'idle'; + let timer; + let promise = PromiseResolve(); + + const afterDelay = () => { + state = 'running'; + promise = promise + .then(() => fn()) + .catch((error) => { + triggerUncaughtException(error, true /* fromPromise */); + }) + .finally(() => { + if (state === 'running') { + state = 'idle'; + } else if (state === 'pending') { + state = 'debouncing'; + timer = setTimeout(afterDelay, delay); + } + }); + }; + + return debouncedFn; + + function debouncedFn(destroy = false) { + if (destroy) { + clearTimeout(timer); + state = 'destroyed'; + } else if (state === 'idle') { + state = 'debouncing'; + timer = setTimeout(afterDelay, delay); + } else if (state === 'debouncing') { + clearTimeout(timer); + timer = setTimeout(afterDelay, delay); + } else if (state === 'running') { + state = 'pending'; + } + + return promise; + } +} + (async () => { emitExperimentalWarning('Watch mode'); try { start(); + const debouncedRestart = debounce(restart, 200); + // eslint-disable-next-line no-unused-vars for await (const _ of on(watcher, 'changed')) { - await restart(); + debouncedRestart(); } + + await debouncedRestart(true); } catch (error) { triggerUncaughtException(error, true /* fromPromise */); }