Skip to content

Commit 8282367

Browse files
authored
Merge pull request #689 from callstack-internal/VickyStash/poc/remove-batching
Remove batching mechanism
2 parents 58604ac + 78408f1 commit 8282367

File tree

11 files changed

+56
-130
lines changed

11 files changed

+56
-130
lines changed

API-INTERNAL.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@
2626
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, evictableKeys)</a></dt>
2727
<dd><p>Sets the initial values for the Onyx store</p>
2828
</dd>
29-
<dt><a href="#maybeFlushBatchUpdates">maybeFlushBatchUpdates()</a></dt>
30-
<dd><p>We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
31-
This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
32-
update operations. Instead of calling the subscribers for each update operation, we batch them together which will
33-
cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.</p>
34-
</dd>
3529
<dt><a href="#reduceCollectionWithSelector">reduceCollectionWithSelector()</a></dt>
3630
<dd><p>Takes a collection of items (eg. {testKey_1:{a:&#39;a&#39;}, testKey_2:{b:&#39;b&#39;}})
3731
and runs it through a reducer function to return a subset of the data according to a selector.
@@ -61,6 +55,9 @@ to the values for those keys (correctly typed) such as <code>[OnyxCollection&lt;
6155
<dd><p>Checks to see if the subscriber&#39;s supplied key
6256
is associated with a collection of keys.</p>
6357
</dd>
58+
<dt><a href="#isCollectionMember">isCollectionMember(key)</a> ⇒</dt>
59+
<dd><p>Checks if a given key is a collection member key (not just a collection key).</p>
60+
</dd>
6461
<dt><a href="#splitCollectionMemberKey">splitCollectionMemberKey(key, collectionKey)</a> ⇒</dt>
6562
<dd><p>Splits a collection member key into the collection key part and the ID part.</p>
6663
</dd>
@@ -98,11 +95,14 @@ run out of storage the least recently accessed key can be removed.</p>
9895
<dt><a href="#getCollectionDataAndSendAsObject">getCollectionDataAndSendAsObject()</a></dt>
9996
<dd><p>Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.</p>
10097
</dd>
98+
<dt><a href="#prepareSubscriberUpdate">prepareSubscriberUpdate(callback)</a></dt>
99+
<dd><p>Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.</p>
100+
</dd>
101101
<dt><a href="#scheduleSubscriberUpdate">scheduleSubscriberUpdate()</a></dt>
102102
<dd><p>Schedules an update that will be appended to the macro task queue (so it doesn&#39;t update the subscribers immediately).</p>
103103
</dd>
104104
<dt><a href="#scheduleNotifyCollectionSubscribers">scheduleNotifyCollectionSubscribers()</a></dt>
105-
<dd><p>This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
105+
<dd><p>This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
106106
so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
107107
subscriber callbacks receive the data in a different format than they normally expect and it breaks code.</p>
108108
</dd>
@@ -230,15 +230,6 @@ Sets the initial values for the Onyx store
230230
| initialKeyStates | initial data to set when `init()` and `clear()` are called |
231231
| evictableKeys | This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal. |
232232

233-
<a name="maybeFlushBatchUpdates"></a>
234-
235-
## maybeFlushBatchUpdates()
236-
We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
237-
This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
238-
update operations. Instead of calling the subscribers for each update operation, we batch them together which will
239-
cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
240-
241-
**Kind**: global function
242233
<a name="reduceCollectionWithSelector"></a>
243234

244235
## reduceCollectionWithSelector()
@@ -304,6 +295,18 @@ Checks to see if the subscriber's supplied key
304295
is associated with a collection of keys.
305296

306297
**Kind**: global function
298+
<a name="isCollectionMember"></a>
299+
300+
## isCollectionMember(key) ⇒
301+
Checks if a given key is a collection member key (not just a collection key).
302+
303+
**Kind**: global function
304+
**Returns**: true if the key is a collection member, false otherwise
305+
306+
| Param | Description |
307+
| --- | --- |
308+
| key | The key to check |
309+
307310
<a name="splitCollectionMemberKey"></a>
308311

309312
## splitCollectionMemberKey(key, collectionKey) ⇒
@@ -402,6 +405,17 @@ run out of storage the least recently accessed key can be removed.
402405
Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.
403406

404407
**Kind**: global function
408+
<a name="prepareSubscriberUpdate"></a>
409+
410+
## prepareSubscriberUpdate(callback)
411+
Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.
412+
413+
**Kind**: global function
414+
415+
| Param | Description |
416+
| --- | --- |
417+
| callback | The keyChanged/keysChanged callback |
418+
405419
<a name="scheduleSubscriberUpdate"></a>
406420

407421
## scheduleSubscriberUpdate()
@@ -415,7 +429,7 @@ scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValu
415429
<a name="scheduleNotifyCollectionSubscribers"></a>
416430

417431
## scheduleNotifyCollectionSubscribers()
418-
This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
432+
This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
419433
so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
420434
subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
421435

API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ applied in the order they were called. Note: `Onyx.set()` calls do not work this
177177
**Example**
178178
```js
179179
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Joe']); // -> ['Joe']
180-
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Jack']
180+
Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack']
181181
Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1}
182182
Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
183183
```

jestSetup.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,3 @@ jest.mock('react-native-nitro-sqlite', () => ({
99
}));
1010

1111
jest.useRealTimers();
12-
13-
const unstable_batchedUpdates_jest = require('react-test-renderer').unstable_batchedUpdates;
14-
require('./lib/batch.native').default = unstable_batchedUpdates_jest;

lib/Onyx.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function init({
6262
// Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
6363
const isKeyCollectionMember = OnyxUtils.isCollectionMember(key);
6464

65-
OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>, undefined, true, isKeyCollectionMember);
65+
OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>, undefined, isKeyCollectionMember);
6666
});
6767
}
6868

lib/OnyxUtils.ts

Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import * as Logger from './Logger';
77
import type Onyx from './Onyx';
88
import cache, {TASK} from './OnyxCache';
99
import * as Str from './Str';
10-
import unstable_batchedUpdates from './batch';
1110
import Storage from './storage';
1211
import type {
1312
CollectionKey,
@@ -76,6 +75,9 @@ type OnyxMethod = ValueOf<typeof METHOD>;
7675
let mergeQueue: Record<OnyxKey, Array<OnyxValue<OnyxKey>>> = {};
7776
let mergeQueuePromise: Record<OnyxKey, Promise<void>> = {};
7877

78+
// Used to schedule subscriber update to the macro task queue
79+
let nextMacrotaskPromise: Promise<void> | null = null;
80+
7981
// Holds a mapping of all the React components that want their state subscribed to a store key
8082
let callbackToStateMapping: Record<string, CallbackToStateMapping<OnyxKey>> = {};
8183

@@ -88,9 +90,6 @@ let onyxKeyToSubscriptionIDs = new Map();
8890
// Optional user-provided key value states set when Onyx initializes or clears
8991
let defaultKeyStates: Record<OnyxKey, OnyxValue<OnyxKey>> = {};
9092

91-
let batchUpdatesPromise: Promise<void> | null = null;
92-
let batchUpdatesQueue: Array<() => void> = [];
93-
9493
// Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
9594
let lastConnectionCallbackData = new Map<number, OnyxValue<OnyxKey>>();
9695

@@ -212,43 +211,6 @@ function sendActionToDevTools(
212211
DevTools.registerAction(utils.formatActionName(method, key), value, key ? {[key]: mergedValue || value} : (value as OnyxCollection<KeyValueMapping[OnyxKey]>));
213212
}
214213

215-
/**
216-
* We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
217-
* This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
218-
* update operations. Instead of calling the subscribers for each update operation, we batch them together which will
219-
* cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
220-
*/
221-
function maybeFlushBatchUpdates(): Promise<void> {
222-
if (batchUpdatesPromise) {
223-
return batchUpdatesPromise;
224-
}
225-
226-
batchUpdatesPromise = new Promise((resolve) => {
227-
/* We use (setTimeout, 0) here which should be called once native module calls are flushed (usually at the end of the frame)
228-
* We may investigate if (setTimeout, 1) (which in React Native is equal to requestAnimationFrame) works even better
229-
* then the batch will be flushed on next frame.
230-
*/
231-
setTimeout(() => {
232-
const updatesCopy = batchUpdatesQueue;
233-
batchUpdatesQueue = [];
234-
batchUpdatesPromise = null;
235-
unstable_batchedUpdates(() => {
236-
for (const applyUpdates of updatesCopy) {
237-
applyUpdates();
238-
}
239-
});
240-
241-
resolve();
242-
}, 0);
243-
});
244-
return batchUpdatesPromise;
245-
}
246-
247-
function batchUpdates(updates: () => void): Promise<void> {
248-
batchUpdatesQueue.push(updates);
249-
return maybeFlushBatchUpdates();
250-
}
251-
252214
/**
253215
* Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
254216
* and runs it through a reducer function to return a subset of the data according to a selector.
@@ -634,7 +596,6 @@ function keysChanged<TKey extends CollectionKeyBase>(
634596
collectionKey: TKey,
635597
partialCollection: OnyxCollection<KeyValueMapping[TKey]>,
636598
partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined,
637-
notifyConnectSubscribers = true,
638599
): void {
639600
// We prepare the "cached collection" which is the entire collection + the new partial data that
640601
// was merged in via mergeCollection().
@@ -670,10 +631,6 @@ function keysChanged<TKey extends CollectionKeyBase>(
670631

671632
// Regular Onyx.connect() subscriber found.
672633
if (typeof subscriber.callback === 'function') {
673-
if (!notifyConnectSubscribers) {
674-
continue;
675-
}
676-
677634
// If they are subscribed to the collection key and using waitForCollectionCallback then we'll
678635
// send the whole cached collection.
679636
if (isSubscribedToCollectionKey) {
@@ -723,7 +680,6 @@ function keyChanged<TKey extends OnyxKey>(
723680
key: TKey,
724681
value: OnyxValue<TKey>,
725682
canUpdateSubscriber: (subscriber?: CallbackToStateMapping<OnyxKey>) => boolean = () => true,
726-
notifyConnectSubscribers = true,
727683
isProcessingCollectionUpdate = false,
728684
): void {
729685
// Add or remove this key from the recentlyAccessedKeys lists
@@ -765,9 +721,6 @@ function keyChanged<TKey extends OnyxKey>(
765721

766722
// Subscriber is a regular call to connect() and provided a callback
767723
if (typeof subscriber.callback === 'function') {
768-
if (!notifyConnectSubscribers) {
769-
continue;
770-
}
771724
if (lastConnectionCallbackData.has(subscriber.subscriptionID) && lastConnectionCallbackData.get(subscriber.subscriptionID) === value) {
772725
continue;
773726
}
@@ -850,6 +803,23 @@ function getCollectionDataAndSendAsObject<TKey extends OnyxKey>(matchingKeys: Co
850803
});
851804
}
852805

806+
/**
807+
* Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.
808+
*
809+
* @param callback The keyChanged/keysChanged callback
810+
* */
811+
function prepareSubscriberUpdate(callback: () => void): Promise<void> {
812+
if (!nextMacrotaskPromise) {
813+
nextMacrotaskPromise = new Promise<void>((resolve) => {
814+
setTimeout(() => {
815+
nextMacrotaskPromise = null;
816+
resolve();
817+
}, 0);
818+
});
819+
}
820+
return Promise.all([nextMacrotaskPromise, Promise.resolve().then(callback)]).then();
821+
}
822+
853823
/**
854824
* Schedules an update that will be appended to the macro task queue (so it doesn't update the subscribers immediately).
855825
*
@@ -862,13 +832,11 @@ function scheduleSubscriberUpdate<TKey extends OnyxKey>(
862832
canUpdateSubscriber: (subscriber?: CallbackToStateMapping<OnyxKey>) => boolean = () => true,
863833
isProcessingCollectionUpdate = false,
864834
): Promise<void> {
865-
const promise = Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber, true, isProcessingCollectionUpdate));
866-
batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false, isProcessingCollectionUpdate));
867-
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
835+
return prepareSubscriberUpdate(() => keyChanged(key, value, canUpdateSubscriber, isProcessingCollectionUpdate));
868836
}
869837

870838
/**
871-
* This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
839+
* This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
872840
* so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
873841
* subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
874842
*/
@@ -877,9 +845,7 @@ function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(
877845
value: OnyxCollection<KeyValueMapping[TKey]>,
878846
previousValue?: OnyxCollection<KeyValueMapping[TKey]>,
879847
): Promise<void> {
880-
const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true));
881-
batchUpdates(() => keysChanged(key, value, previousValue, false));
882-
return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
848+
return prepareSubscriberUpdate(() => keysChanged(key, value, previousValue));
883849
}
884850

885851
/**
@@ -1692,7 +1658,6 @@ function clearOnyxUtilsInternals() {
16921658
mergeQueuePromise = {};
16931659
callbackToStateMapping = {};
16941660
onyxKeyToSubscriptionIDs = new Map();
1695-
batchUpdatesQueue = [];
16961661
lastConnectionCallbackData = new Map();
16971662
}
16981663

@@ -1704,8 +1669,6 @@ const OnyxUtils = {
17041669
getDeferredInitTask,
17051670
initStoreValues,
17061671
sendActionToDevTools,
1707-
maybeFlushBatchUpdates,
1708-
batchUpdates,
17091672
get,
17101673
getAllKeys,
17111674
getCollectionKeys,
@@ -1763,10 +1726,6 @@ GlobalSettings.addGlobalSettingsChangeListener(({enablePerformanceMetrics}) => {
17631726

17641727
// @ts-expect-error Reassign
17651728
initStoreValues = decorateWithMetrics(initStoreValues, 'OnyxUtils.initStoreValues');
1766-
// @ts-expect-error Reassign
1767-
maybeFlushBatchUpdates = decorateWithMetrics(maybeFlushBatchUpdates, 'OnyxUtils.maybeFlushBatchUpdates');
1768-
// @ts-expect-error Reassign
1769-
batchUpdates = decorateWithMetrics(batchUpdates, 'OnyxUtils.batchUpdates');
17701729
// @ts-expect-error Complex type signature
17711730
get = decorateWithMetrics(get, 'OnyxUtils.get');
17721731
// @ts-expect-error Reassign

lib/batch.native.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/batch.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

package-lock.json

Lines changed: 0 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)