Summary
⚠️ Breaking change — this is the intentional fix that accompanies bug #36. Listed separately as a breaking change because it alters the observable contract of isObserving and instance reuse patterns.
Make wait() automatically disconnect the internal observer and clear all internal state as soon as the promise resolves, without requiring the caller to call clear() manually.
Current behaviour (bug)
After wait() resolves, isObserving returns true and the MutationObserver keeps running. Callers who reuse the instance must call clear() explicitly before the next wait() or watch(). Failing to do so is not surfaced as an error.
Proposed behaviour
const obs = new DOMObserver()
const { node } = await obs.wait('#foo')
console.log(obs.isObserving) // false — auto-cleared
The observer is disconnected, the timeout is cleared, and all internal state is reset to the same state as a freshly constructed instance.
Breaking scenarios
1. Code that assumes isObserving is true after resolution
Unlikely in practice, but code that branches on obs.isObserving immediately after await obs.wait(...) would change behaviour.
2. Code that calls clear() after wait() as a precaution
Double-calling clear() is safe (it is idempotent), so this is not technically broken — it just becomes a no-op.
3. Code that relies on the stale timeout firing after resolution
Currently, if wait(target, { timeout: 5000 }) resolves at 100ms, the timer fires at 5000ms and calls this.clear(). Some code might inadvertently depend on this cleanup being deferred. After this change, cleanup happens at 100ms.
Implementation
In wait(), change settle():
const settle = (value: WaitResult) => {
cleanup() // remove abort listener
this.clear() // disconnect observer, clear timeout — NEW
resolve(value)
}
Migration
No code changes needed for most callers. The pattern await obs.wait(...); obs.clear() continues to work (second clear() is a no-op). Code that explicitly checked obs.isObserving === true after wait() would need updating.
Summary
Make
wait()automatically disconnect the internal observer and clear all internal state as soon as the promise resolves, without requiring the caller to callclear()manually.Current behaviour (bug)
After
wait()resolves,isObservingreturnstrueand theMutationObserverkeeps running. Callers who reuse the instance must callclear()explicitly before the nextwait()orwatch(). Failing to do so is not surfaced as an error.Proposed behaviour
The observer is disconnected, the timeout is cleared, and all internal state is reset to the same state as a freshly constructed instance.
Breaking scenarios
1. Code that assumes
isObservingistrueafter resolutionUnlikely in practice, but code that branches on
obs.isObservingimmediately afterawait obs.wait(...)would change behaviour.2. Code that calls
clear()afterwait()as a precautionDouble-calling
clear()is safe (it is idempotent), so this is not technically broken — it just becomes a no-op.3. Code that relies on the stale timeout firing after resolution
Currently, if
wait(target, { timeout: 5000 })resolves at 100ms, the timer fires at 5000ms and callsthis.clear(). Some code might inadvertently depend on this cleanup being deferred. After this change, cleanup happens at 100ms.Implementation
In
wait(), changesettle():Migration
No code changes needed for most callers. The pattern
await obs.wait(...); obs.clear()continues to work (secondclear()is a no-op). Code that explicitly checkedobs.isObserving === trueafterwait()would need updating.