Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add batch function for batching writes #306

Open
wants to merge 20 commits into
base: next
Choose a base branch
from

Conversation

russelldavis
Copy link

Supersedes #298. See the description of that PR for the overview. Changes in this version:

  • Based on next branch instead of main
  • Fixes stale values and listeners being called multiple times when batching
  • Removes notify from the public types, adds overridable isEqual function as alternative
  • Fixes listenKeys/subscribeKeys to work with batch
  • Adds documentation

There are more details about each of those bullet points in the commit messages. It's probably best to start by looking at the commits individually rather than all the changes together.

gismya and others added 17 commits April 24, 2024 17:13
* feat: add subscribeKeys function

* Update size limit

* Update listen-keys/index.d.ts

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>

* Update listen-keys/index.js

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>

* Formatting reversal readme

---------

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>
* Export `setByKey` function

* eslint sort imports
* Prevent stale computed values

* Remove atom levels

The previous commit's solution to the stale atom problem also solves the
diamond problem without needing special handling of atom levels.

* Optimize computed dependency check using epochs

* Update size-limit

---------

Co-authored-by: Andrey Sitnik <andrey@sitnik.ru>
The new code in #380 needed to be updated to reflect the new lower
number of items in listenerQueue per listener.
The benchmark for `computed` on my machine increased
from 3,815 ops/sec to 5,193 ops/sec
Previously, calling setKey on a $deepMap would mutate the value
in-place. This can cause various problems, for example nanostores#250 and nanostores#290.
Those were both solved with workarounds, however, this solves it in a
better way, by not mutating the deepMap in the first place.

Some advantages of this approach:
* No need for structuredClone
* The oldValue passed to listeners is the same object reference as the
value that was previously passed. This is how immutable stores usually
work, so it will align better with expectations, cause fewer issues, and
allow for new use cases, like the upcoming batching PR which needs that.
* Copies were already being made at each nesting level of the key being
set, they just weren't being used (so maybe this was the original
intent?). Either way, that means there's no new work being done, and
perf will actually improve since we no longer need to call
structuredClone on oldValue.

As part of this change, when a key path extends past the end of an array
the result will be a sparse array rather than filling it with undefined.
It would have required extra code (and slower perf) to keep the old
behavior, and I don't see a good reason for it. The new behavior aligns
with what JS does natively, and with other stores like SolidJS. But let
me know if that's a problem.
1) Stale values could be passed to listeners
2) Listeners could be called multiple times in a row at the end of a
batch when an atom changed multiple times during the batch
As of the previous commit, each listener has to track oldValue anyway
to avoid issues with batching, so the caller of `notify` no longer needs
to provide it.

Also removed `notify` as a publicly exposed function -- now that
listeners are automatically skipped when the value is the same as the
last call, calling `notify` with an unchanged value won't trigger the
listeners, which is the reason it was made public in the first place
(see https://github.com/orgs/nanostores/discussions/276).

A new way to solve that problem (via a custom isEqual function) will be
added in the next commit.
With the addition of `batch`, the `changedKey` argument to `listen`
can't be relied on. It also improves on the old approach by not calling
listeners when unrelated keys are changed via `set`.

Listening to deep keys is now done via separate functions
`listenKeyPaths` and `subscribeKeyPaths`, because:

* It allows better performance for the more common case of top-level
  keys
* It allows listening to top-level keys that have dots in them, (e.g.
  imagine a single key with the value "foo.bar")
* It allows for better type definitions for the keyPaths version
  (not yet changed)
@@ -210,20 +210,20 @@ Setting `undefined` will remove optional key:
$profile.setKey('email', undefined)
```

Store’s listeners will receive third argument with changed key.
Deprecated: a store’s listeners will receive a third argument `changedKey` when
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, many people really loves this API. They will not like it.

Instead of deprecating let’s just explain that it will not work with batch, because there is already a case, when key doesn’t work and people is OK with it.

gets updated. If you are fine with waiting until the end of a tick, you can
use `batched`. The only difference with `computed` is that it will wait until
the end of a tick to update itself.
### Batching
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Batching
### Batching

lc: 0,
listen(listener) {
listen(_listener) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_var is not cool, let’s find a better name for next callback.

@@ -92,14 +92,14 @@
"import": {
"./index.js": "{ atom }"
},
"limit": "265 B"
"limit": "336 B"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The price is too big for this feature.

Let’s think how we can avoid it. Maybe we can avoid isEqual?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants