Repository: pmndrs/valtio (ts)

Issue:
10x slower when dev console is open
I'm investigating why there's some extra overhead when the console (in Chrome) is open.
That's even when not using `devtools( )` to connect to the redux extension.

I see the issue in a real application with a few watchers and an use case where there are thousand of updates at once (trying out to use valtio in place of a custom store). 

Here's an attempt to create a reproducible case: https://jsfiddle.net/pedroteixeira/62c1xbnd/15/

Just run with the console closed and then run with it open (you'll be able to see the previous run log in the console). 

What I observe:

- When console is not open: it takes less than 20ms 
- When console is open, it takes 10x more than that to complete (~200ms)
- Changing test to run 350k updates (instead of 35k) keeps ~10x difference 

Any hints what might be causing this overhead? Not sure if there exists debug-related settings for proxies (for ex). Researching here. Any help is appreciated. Thanks in advance.


Pull request description:
fix(core): improve batching
close #284 

This avoids creating extra promises in a batch. Hopefully, it will improve the performance.

Patch:
diff --git a/src/utils/watch.ts b/src/utils/watch.ts
index 69969dd..5251baa 100644
--- a/src/utils/watch.ts
+++ b/src/utils/watch.ts
@@ -2,6 +2,9 @@ import { subscribe } from '../vanilla'
 
 type WatchGet = <T extends object>(value: T) => T
 type WatchCallback = (get: WatchGet) => (() => void) | void | undefined
+type WatchOptions = {
+  sync?: boolean
+}
 
 let currentCleanups: Set<() => void> | undefined
 
@@ -28,7 +31,10 @@ let currentCleanups: Set<() => void> | undefined
  * @returns A cleanup function that stops the callback from reevaluating and
  * also performs cleanups registered into `watch`.
  */
-export const watch = (callback: WatchCallback): (() => void) => {
+export const watch = (
+  callback: WatchCallback,
+  options?: WatchOptions
+): (() => void) => {
   const cleanups = new Set<() => void>()
   const subscriptions = new Set<object>()
 
@@ -72,7 +78,7 @@ export const watch = (callback: WatchCallback): (() => void) => {
 
     // Subscribe to all collected proxies
     subscriptions.forEach((proxy) => {
-      const clean = subscribe(proxy, revalidate)
+      const clean = subscribe(proxy, revalidate, options?.sync)
       cleanups.add(clean)
     })
   }
diff --git a/src/vanilla.ts b/src/vanilla.ts
index 8d88640..50d13ab 100644
--- a/src/vanilla.ts
+++ b/src/vanilla.ts
@@ -216,20 +216,20 @@ export const subscribe = (
   ) {
     console.warn('Please use proxy object')
   }
-  let pendingVersion = 0
+  let promise: Promise<void> | undefined
   const ops: Op[] = []
-  const listener: Listener = (op, nextVersion) => {
+  const listener: Listener = (op) => {
     ops.push(op)
     if (notifyInSync) {
       callback(ops.splice(0))
       return
     }
-    pendingVersion = nextVersion
-    Promise.resolve().then(() => {
-      if (nextVersion === pendingVersion) {
+    if (!promise) {
+      promise = Promise.resolve().then(() => {
+        promise = undefined
         callback(ops.splice(0))
-      }
-    })
+      })
+    }
   }
   proxyObject[LISTENERS].add(listener)
   return () => {


Test patch:
diff --git a/tests/derive.test.tsx b/tests/derive.test.tsx
index abc925a..79557aa 100644
--- a/tests/derive.test.tsx
+++ b/tests/derive.test.tsx
@@ -232,7 +232,7 @@ it('nested emulation with derive', async () => {
   })
   expect(computeDouble).toBeCalledTimes(2)
   await Promise.resolve()
-  expect(callback).toBeCalledTimes(2)
+  expect(callback).toBeCalledTimes(1)
 
   state.text = 'a'
   await Promise.resolve()
@@ -241,7 +241,8 @@ it('nested emulation with derive', async () => {
     math: { count: 1, doubled: 2 },
   })
   expect(computeDouble).toBeCalledTimes(2)
-  expect(callback).toBeCalledTimes(3)
+  await Promise.resolve()
+  expect(callback).toBeCalledTimes(2)
 })
 
 it('derive with array.pop', async () => {