Skip to content

Commit 84ea7e8

Browse files
committed
Make OnyxUtils get functions synchronous
1 parent bcfee2c commit 84ea7e8

8 files changed

Lines changed: 308 additions & 521 deletions

File tree

lib/Onyx.ts

Lines changed: 97 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,16 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
220220
}
221221
mergeQueue[key] = [changes];
222222

223-
mergeQueuePromise[key] = OnyxUtils.get(key).then((existingValue) => {
223+
mergeQueuePromise[key] = Promise.resolve().then(() => {
224224
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
225225
if (mergeQueue[key] == null) {
226226
return Promise.resolve();
227227
}
228228

229+
// Read the existing value at merge application time (not at queue time) so that
230+
// any intervening synchronous cache updates (e.g. from mergeCollection) are picked up.
231+
const existingValue = OnyxUtils.get(key);
232+
229233
try {
230234
const validChanges = mergeQueue[key].filter((change) => {
231235
const {isCompatible, existingValueType, newValueType, isEmptyArrayCoercion} = utils.checkCompatibilityWithExistingValue(change, existingValue);
@@ -313,92 +317,98 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
313317
const defaultKeyStates = OnyxUtils.getDefaultKeyStates();
314318
const initialKeys = Object.keys(defaultKeyStates);
315319

316-
const promise = OnyxUtils.getAllKeys()
317-
.then((cachedKeys) => {
318-
cache.clearNullishStorageKeys();
319-
320-
const keysToBeClearedFromStorage: OnyxKey[] = [];
321-
const keyValuesToResetIndividually: KeyValueMapping = {};
322-
// We need to store old and new values for collection keys to properly notify subscribers when clearing Onyx
323-
// because the notification process needs the old values in cache but at that point they will be already removed from it.
324-
const keyValuesToResetAsCollection: Record<
325-
OnyxKey,
326-
{oldValues: Record<string, KeyValueMapping[OnyxKey] | undefined>; newValues: Record<string, KeyValueMapping[OnyxKey] | undefined>}
327-
> = {};
328-
329-
const allKeys = new Set([...cachedKeys, ...initialKeys]);
330-
331-
// The only keys that should not be cleared are:
332-
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
333-
// status, or activeClients need to remain in Onyx even when signed out)
334-
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
335-
// to null would cause unknown behavior)
336-
// 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
337-
for (const key of allKeys) {
338-
const isKeyToPreserve = keysToPreserve.some((preserveKey) => OnyxKeys.isKeyMatch(preserveKey, key));
339-
const isDefaultKey = key in defaultKeyStates;
340-
341-
// If the key is being removed or reset to default:
342-
// 1. Update it in the cache
343-
// 2. Figure out whether it is a collection key or not,
344-
// since collection key subscribers need to be updated differently
345-
if (!isKeyToPreserve) {
346-
const oldValue = cache.get(key);
347-
const newValue = defaultKeyStates[key] ?? null;
348-
if (newValue !== oldValue) {
349-
cache.set(key, newValue);
350-
351-
const collectionKey = OnyxKeys.getCollectionKey(key);
352-
353-
if (collectionKey) {
354-
if (!keyValuesToResetAsCollection[collectionKey]) {
355-
keyValuesToResetAsCollection[collectionKey] = {oldValues: {}, newValues: {}};
356-
}
357-
keyValuesToResetAsCollection[collectionKey].oldValues[key] = oldValue;
358-
keyValuesToResetAsCollection[collectionKey].newValues[key] = newValue ?? undefined;
359-
} else {
360-
keyValuesToResetIndividually[key] = newValue ?? undefined;
361-
}
362-
}
363-
}
320+
const cachedKeys = OnyxUtils.getAllKeys();
321+
cache.clearNullishStorageKeys();
364322

365-
if (isKeyToPreserve || isDefaultKey) {
366-
continue;
367-
}
323+
// Clear pending merge queues so that any in-flight Onyx.merge() calls
324+
// don't overwrite the default values we're about to set.
325+
const mergeQueue = OnyxUtils.getMergeQueue();
326+
const mergeQueuePromise = OnyxUtils.getMergeQueuePromise();
327+
for (const key of Object.keys(mergeQueue)) {
328+
delete mergeQueue[key];
329+
delete mergeQueuePromise[key];
330+
}
368331

369-
// If it isn't preserved and doesn't have a default, we'll remove it
370-
keysToBeClearedFromStorage.push(key);
332+
const keysToBeClearedFromStorage: OnyxKey[] = [];
333+
const keyValuesToResetIndividually: KeyValueMapping = {};
334+
// We need to store old and new values for collection keys to properly notify subscribers when clearing Onyx
335+
// because the notification process needs the old values in cache but at that point they will be already removed from it.
336+
const keyValuesToResetAsCollection: Record<
337+
OnyxKey,
338+
{oldValues: Record<string, KeyValueMapping[OnyxKey] | undefined>; newValues: Record<string, KeyValueMapping[OnyxKey] | undefined>}
339+
> = {};
340+
341+
const allKeys = new Set([...cachedKeys, ...initialKeys]);
342+
343+
// The only keys that should not be cleared are:
344+
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
345+
// status, or activeClients need to remain in Onyx even when signed out)
346+
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
347+
// to null would cause unknown behavior)
348+
// 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
349+
for (const key of allKeys) {
350+
const isKeyToPreserve = keysToPreserve.some((preserveKey) => OnyxKeys.isKeyMatch(preserveKey, key));
351+
const isDefaultKey = key in defaultKeyStates;
352+
353+
// If the key is being removed or reset to default:
354+
// 1. Update it in the cache
355+
// 2. Figure out whether it is a collection key or not,
356+
// since collection key subscribers need to be updated differently
357+
if (!isKeyToPreserve) {
358+
const oldValue = cache.get(key);
359+
const newValue = defaultKeyStates[key] ?? null;
360+
if (newValue !== oldValue) {
361+
cache.set(key, newValue);
362+
363+
const collectionKey = OnyxKeys.getCollectionKey(key);
364+
365+
if (collectionKey) {
366+
if (!keyValuesToResetAsCollection[collectionKey]) {
367+
keyValuesToResetAsCollection[collectionKey] = {oldValues: {}, newValues: {}};
368+
}
369+
keyValuesToResetAsCollection[collectionKey].oldValues[key] = oldValue;
370+
keyValuesToResetAsCollection[collectionKey].newValues[key] = newValue ?? undefined;
371+
} else {
372+
keyValuesToResetIndividually[key] = newValue ?? undefined;
373+
}
371374
}
375+
}
372376

373-
// Exclude RAM-only keys to prevent them from being saved to storage
374-
const defaultKeyValuePairs = Object.entries(
375-
Object.keys(defaultKeyStates)
376-
.filter((key) => !keysToPreserve.some((preserveKey) => OnyxKeys.isKeyMatch(preserveKey, key)) && !OnyxKeys.isRamOnlyKey(key))
377-
.reduce((obj: KeyValueMapping, key) => {
378-
// eslint-disable-next-line no-param-reassign
379-
obj[key] = defaultKeyStates[key];
380-
return obj;
381-
}, {}),
382-
);
377+
if (isKeyToPreserve || isDefaultKey) {
378+
continue;
379+
}
383380

384-
// Remove only the items that we want cleared from storage, and reset others to default
385-
for (const key of keysToBeClearedFromStorage) cache.drop(key);
386-
return Storage.removeItems(keysToBeClearedFromStorage)
387-
.then(() => connectionManager.refreshSessionID())
388-
.then(() => Storage.multiSet(defaultKeyValuePairs))
389-
.then(() => {
390-
DevTools.clearState(keysToPreserve);
391-
392-
// Notify the subscribers for each key/value group so they can receive the new values
393-
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
394-
OnyxUtils.keyChanged(key, value);
395-
}
396-
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
397-
OnyxUtils.keysChanged(key, value.newValues, value.oldValues);
398-
}
399-
});
400-
})
401-
.then(() => undefined);
381+
// If it isn't preserved and doesn't have a default, we'll remove it
382+
keysToBeClearedFromStorage.push(key);
383+
}
384+
385+
// Exclude RAM-only keys to prevent them from being saved to storage
386+
const defaultKeyValuePairs = Object.entries(
387+
Object.keys(defaultKeyStates)
388+
.filter((key) => !keysToPreserve.some((preserveKey) => OnyxKeys.isKeyMatch(preserveKey, key)) && !OnyxKeys.isRamOnlyKey(key))
389+
.reduce((obj: KeyValueMapping, key) => {
390+
// eslint-disable-next-line no-param-reassign
391+
obj[key] = defaultKeyStates[key];
392+
return obj;
393+
}, {}),
394+
);
395+
396+
// Remove only the items that we want cleared from storage, and reset others to default
397+
for (const key of keysToBeClearedFromStorage) cache.drop(key);
398+
const promise = Storage.removeItems(keysToBeClearedFromStorage)
399+
.then(() => connectionManager.refreshSessionID())
400+
.then(() => Storage.multiSet(defaultKeyValuePairs))
401+
.then(() => {
402+
DevTools.clearState(keysToPreserve);
403+
404+
// Notify the subscribers for each key/value group so they can receive the new values
405+
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
406+
OnyxUtils.keyChanged(key, value);
407+
}
408+
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
409+
OnyxUtils.keysChanged(key, value.newValues, value.oldValues);
410+
}
411+
});
402412

403413
return cache.captureTask(TASK.CLEAR, promise) as Promise<void>;
404414
});
@@ -519,6 +529,11 @@ function update<TKey extends OnyxKey>(data: Array<OnyxUpdate<TKey>>): Promise<vo
519529
},
520530
);
521531

532+
// Set operations must run before merge operations so their cache writes are
533+
// visible when mergeCollectionWithPatches reads previous values synchronously.
534+
if (!utils.isEmptyObject(batchedCollectionUpdates.set)) {
535+
promises.push(() => OnyxUtils.partialSetCollection({collectionKey, collection: batchedCollectionUpdates.set as OnyxSetCollectionInput<OnyxKey>}));
536+
}
522537
if (!utils.isEmptyObject(batchedCollectionUpdates.merge)) {
523538
promises.push(() =>
524539
OnyxUtils.mergeCollectionWithPatches({
@@ -529,9 +544,6 @@ function update<TKey extends OnyxKey>(data: Array<OnyxUpdate<TKey>>): Promise<vo
529544
}),
530545
);
531546
}
532-
if (!utils.isEmptyObject(batchedCollectionUpdates.set)) {
533-
promises.push(() => OnyxUtils.partialSetCollection({collectionKey, collection: batchedCollectionUpdates.set as OnyxSetCollectionInput<OnyxKey>}));
534-
}
535547
}
536548

537549
for (const [key, operations] of Object.entries(updateQueue)) {

lib/OnyxCache.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,17 @@ class OnyxCache {
316316
* @param isCollectionKeyFn - Function to determine if a key is a collection key
317317
* @param getAllKeysFn - Function to get all keys, defaults to Storage.getAllKeys
318318
*/
319-
addEvictableKeysToRecentlyAccessedList(isCollectionKeyFn: (key: OnyxKey) => boolean, getAllKeysFn: () => Promise<Set<OnyxKey>>): Promise<void> {
320-
return getAllKeysFn().then((keys: Set<OnyxKey>) => {
321-
for (const evictableKey of this.evictionAllowList) {
322-
for (const key of keys) {
323-
if (!OnyxKeys.isKeyMatch(evictableKey, key)) {
324-
continue;
325-
}
326-
327-
this.addLastAccessedKey(key, isCollectionKeyFn(key));
319+
addEvictableKeysToRecentlyAccessedList(isCollectionKeyFn: (key: OnyxKey) => boolean, getAllKeysFn: () => Set<OnyxKey>): void {
320+
const keys = getAllKeysFn();
321+
for (const evictableKey of this.evictionAllowList) {
322+
for (const key of keys) {
323+
if (!OnyxKeys.isKeyMatch(evictableKey, key)) {
324+
continue;
328325
}
326+
327+
this.addLastAccessedKey(key, isCollectionKeyFn(key));
329328
}
330-
});
329+
}
331330
}
332331

333332
/**

0 commit comments

Comments
 (0)