Skip to content

Commit

Permalink
feat(store): async batch operations (#480)
Browse files Browse the repository at this point in the history
* feat(store): async batch operations

Added emitOnceAsync for asynchronous batching of store updates

closes #478

* observable support
  • Loading branch information
LoaderB0T committed Sep 15, 2023
1 parent c92beb1 commit 8d30ccb
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 10 deletions.
79 changes: 73 additions & 6 deletions docs/docs/miscellaneous/batching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@ store.update(
setProp('count', 1),
addEntities([todo, todo]),
deleteEntities(1)
)
);
```

In this case, subscribers will only receive **one** emission instead of three.

## emitOnce

There are cases where you have multiple update functions of the **same** store that you want to batch together. To do so you can use the `emitOnce` function:

```ts title=todos.repository.ts
export function updateCount() {
store.update(
setProp('count', 1),
)
);
}

export function updateUser() {
store.update(
setProp('user', null),
)
);
}
```

Expand All @@ -35,7 +37,7 @@ import { updateCount, updateUser } from './todos.repository';
emitOnce(() => {
updateCount();
updateUser();
})
});
```

In this case, subscribers will only receive **one** emission instead of two.
Expand All @@ -47,7 +49,7 @@ export function restoreFilters() {
emitOnce(() => {
store.update(
setProp('filters', null),
)
);
resetPagination();
});
}
Expand All @@ -64,7 +66,7 @@ export function resetSort() {
export function resetPagination() {
store.update(
setProp('pagination', null),
)
);
}
```

Expand All @@ -79,3 +81,68 @@ emitOnce(() => {
```

In this case, subscribers will only receive **one** emission instead of two.

## emitOnceAsync

In some cases, you might need to use `emitOnce` with async functions or observables. To do so, you can use `emitOnceAsync`:

```ts title=todos.repository.ts
export async function updateCount() {
const newCount = await fetchCount(); // Fetch count from API
store.update(setProp('count', newCount));
}

export async function updateUser() {
const newUser = await fetchUser(); // Fetch user from API
store.update(setProp('user', newUser));
}

export function clearCount() {
store.update(setProp('user', null));
}

export function clearUser() {
store.update(setProp('user', null));
}
```

```ts
import { emitOnceAsync } from '@elf/store';
import { updateCount, updateUser } from './todos.repository';

await emitOnceAsync(async () => {
await updateCount();
await updateUser();
});
```

In this case, subscribers will also only receive **one** emission instead of two.

You can also use `emitOnce` and `emitOnceAsync` inside another `emitOnceAsync`:

```ts
import { emitOnce, emitOnceAsync } from '@elf/store';
import { updateCount, updateUser } from './todos.repository';

async function updateCountAndUser() {
await emitOnceAsync(async () => {
await updateCount();
await updateUser();
});
}

await emitOnceAsync(async () => {
emitOnce(() => {
clearCount();
clearUser();
});
await updateCountAndUser();
});
```

You can also provide an observable to `emitOnceAsync`, in this case, the store will only update when the observable emits its **first** value.

Using `emitOnceAsync` inside `emitOnce` will not work as expected because `emitOnce` will not wait for the async function to finish.

Use `emitOnceAsync` with caution, the store will not update until the async function finishes or the observable emits its first value.
If your async function or observable takes too long to finish, the app might appear unresponsive.
2 changes: 1 addition & 1 deletion packages/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ export {
isString,
isUndefined,
} from './lib/utils';
export { emitOnce } from './lib/batch';
export { emitOnce, emitOnceAsync } from './lib/batch';
export { isDev, enableElfProdMode } from './lib/env';
Loading

0 comments on commit 8d30ccb

Please sign in to comment.