Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ value will be saved to storage after the default value.</p>
<dd><p>Sets a collection by replacing all existing collection members with new values.
Any existing collection members not included in the new data will be removed.</p>
</dd>
<dt><a href="#get">get(key)</a> ⇒</dt>
<dd><p>Reads the current value of an Onyx key once, without subscribing to it. Use <code>useOnyx()</code> or
<code>Onyx.connectWithoutView()</code> when the value has to stay current.</p>
<p>The result is the cached object itself rather than a copy, so treat it as read-only. A write that
is still queued when <code>get()</code> is called is not visible to it, so await the write before reading.</p>
<p>A collection with no members resolves to <code>{}</code>, while a collection read on an empty store resolves
to <code>undefined</code>.</p>
</dd>
</dl>

<a name="init"></a>
Expand Down Expand Up @@ -257,3 +265,27 @@ Onyx.setCollection(ONYXKEYS.COLLECTION.REPORT, {
[`${ONYXKEYS.COLLECTION.REPORT}2`]: report2,
});
```
<a name="get"></a>

## get(key) ⇒
Reads the current value of an Onyx key once, without subscribing to it. Use `useOnyx()` or
`Onyx.connectWithoutView()` when the value has to stay current.

The result is the cached object itself rather than a copy, so treat it as read-only. A write that
is still queued when `get()` is called is not visible to it, so await the write before reading.

A collection with no members resolves to `{}`, while a collection read on an empty store resolves
to `undefined`.

**Kind**: global function
**Returns**: The current value, or `undefined` if the key has none.

| Param | Description |
| --- | --- |
| key | ONYXKEY to read, either a collection key or a single key |

**Example**
```js
const report = await Onyx.get(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const allReports = await Onyx.get(ONYXKEYS.COLLECTION.REPORT);
```
41 changes: 41 additions & 0 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,46 @@ function setCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, coll
return OnyxUtils.afterInit(() => OnyxUtils.setCollectionWithRetry({collectionKey, collection}));
}

/**
* Reads the current value of an Onyx key once, without subscribing to it. Use `useOnyx()` or
* `Onyx.connectWithoutView()` when the value has to stay current.
*
* The result is the cached object itself rather than a copy, so treat it as read-only: mutating it would
* be seen by every other reader of that key. This is a convention rather than a type rule, because
* `useOnyx()` hands back that same object typed mutable and the two surfaces should not disagree. A write
* that is still queued when `get()` is called is not visible to it, so await the write before reading.
*
* A collection with no members resolves to `{}`, while a collection read on an empty store resolves
* to `undefined`.
*
* @example
* const report = await Onyx.get(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
* const allReports = await Onyx.get(ONYXKEYS.COLLECTION.REPORT);
*
* @param key ONYXKEY to read, either a collection key or a single key
* @returns The current value, or `undefined` if the key has none.
*/
function get<TKey extends OnyxKey>(key: TKey): Promise<OnyxValue<TKey>> {
return OnyxUtils.afterInit(() => {
if (OnyxKeys.isCollectionKey(key)) {
const cachedCollection = OnyxUtils.tryGetCachedValue(key);

if (cachedCollection) {
return Promise.resolve(cachedCollection as OnyxValue<TKey>);
}

// Only reached on a cold key index, since that is the one case tryGetCachedValue cannot answer.
return OnyxUtils.getAllKeys()
.then((allKeys) => OnyxUtils.multiGet([...allKeys].filter((memberKey) => OnyxKeys.isCollectionMemberKey(key, memberKey))))
.then(() => OnyxUtils.tryGetCachedValue(key) as OnyxValue<TKey>);
}

// OnyxUtils.get is cache-first and already guards RAM-only keys. A key that storage has never
// held resolves to null there, which the public surface reports as undefined.
return OnyxUtils.get(key).then((value) => (value ?? undefined) as OnyxValue<TKey>);
});
}

const Onyx = {
METHOD: OnyxUtils.METHOD,
connect,
Expand All @@ -624,6 +664,7 @@ const Onyx = {
update,
clear,
init,
get,
registerLogger: Logger.registerLogger,
};

Expand Down
24 changes: 24 additions & 0 deletions tests/perf-test/Onyx.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ describe('Onyx', () => {
});
});

describe('get', () => {
const COLLECTION_READS = 1000;

test('1k calls for the whole collection of 10k heavy objects', async () => {
await measureAsyncFunction(() => Promise.all(Array.from({length: COLLECTION_READS}, () => Onyx.get(collectionKey))), {
beforeEach: async () => {
await Onyx.multiSet(mockedReportActionsMap);
},
afterEach: clearOnyxAfterEachMeasure,
});
});

test('10k calls for individual collection members', async () => {
const memberKeys = Object.keys(mockedReportActionsMap);

await measureAsyncFunction(() => Promise.all(memberKeys.map((memberKey) => Onyx.get(memberKey))), {
beforeEach: async () => {
await Onyx.multiSet(mockedReportActionsMap);
},
afterEach: clearOnyxAfterEachMeasure,
});
});
});

describe('update', () => {
test('one call with 5k sets and 5k merges updates', async () => {
const changedReportActions = Object.fromEntries(Object.entries(mockedReportActionsMap).map(([k, v]) => [k, createRandomReportAction(Number(v.reportActionID))] as const));
Expand Down
Loading
Loading