Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sveltetools/svelte-asyncable
Asyncable store for Svelte 3 which is store a value as promise.
https://github.com/sveltetools/svelte-asyncable
Last synced: 27 days ago
JSON representation
Asyncable store for Svelte 3 which is store a value as promise.
- Host: GitHub
- URL: https://github.com/sveltetools/svelte-asyncable
- Owner: sveltetools
- Created: 2019-11-05T22:43:46.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2024-06-18T01:18:28.000Z (6 months ago)
- Last Synced: 2024-07-18T15:43:36.551Z (5 months ago)
- Language: JavaScript
- Homepage:
- Size: 2.09 MB
- Stars: 168
- Watchers: 5
- Forks: 4
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
- awesome-svelte-stores - svelte-asyncable
- awesome-svelte - svelte-asyncable - The Svelte store contract with support for asynchronous values. (State Libraries / Mobile)
README
# Super tiny, declarative, optimistic, async store for SvelteJS.
[![NPM version](https://img.shields.io/npm/v/svelte-asyncable.svg?style=flat)](https://www.npmjs.com/package/svelte-asyncable) [![NPM downloads](https://img.shields.io/npm/dm/svelte-asyncable.svg?style=flat)](https://www.npmjs.com/package/svelte-asyncable)
## Features
- Extends the [Svelete store contract](https://svelte.dev/docs#4_Prefix_stores_with_$_to_access_their_values) with support for asynchronous values.
- Store contains a Promise of a value.
- Lazy-initialization on-demand.
- Transparent and declarative way to describing side-effects.
- Lets you update the async data without manual resolving.
- Can derive to the other store(s).
- Immutable from the box.
- Optimistic UI pattern included.
- Result of asynchronous call cached for lifetime of last surviving subscriber.## Install
```bash
npm i svelte-asyncable --save
``````bash
yarn add svelte-asyncable
```CDN: [UNPKG](https://unpkg.com/svelte-asyncable/) | [jsDelivr](https://cdn.jsdelivr.net/npm/svelte-asyncable/) (available as `window.Asyncable`)
If you are **not** using ES6, instead of importing add
```html
```
just before closing body tag.
## Usage
### Store with async side-effect that works as a `getter`.
Create your async store with the `asyncable` constructor. It takes a callback or `getter` that allows you to establish an initial value of the store.
```javascript
import { asyncable } from 'svelte-asyncable';const user = asyncable(async () => {
const res = await fetch('/user/me');
return res.json();
});
```Please note, the result of this callback is not evaluated until it is first subscribed to or otherwise requested. (lazy approach).
There are two methods to access the stored promise, `subscribe` and `get`.
`subscribe` is a reactive subscription familiar from the Svelte store contract:
```javascript
user.subscribe(async userStore => {
console.log('user', await userStore); // will be printed after each side-effect
});
````get` returns a copy of the promise that will not update with the store:
```javascript
// a point in time copy of the promise in the storeconst userStore = await user.get();
```Please note, the subscription callback will be triggered with the actual value only after a side-effect.
If the `getter` or callback provided to `asyncable` returns explicit `undefined` (or just return is omitted), the current value of the store won't updated. This may be useful, if we wish to only conditionally update the store. For example, when using `svelte-pathfinder` if `$path` or `$query` are updated, we may only wish to update the posts store on when in the posts route:
```javascript
const posts = asyncable(($path, $query) => {
if ($path.toString() === '/posts') {
return fetch(`/posts?page=${$query.params.page || 1}`).then(res => res.json());
}
},
null,
[ path, query ]
);
```### Store with async side-effect that works as a `setter`.
You can also pass async `setter` callback as a second argument. This function will be is triggered on each update/set operation but not after a `getter` call and receives the new and previous value of the store:
```javascript
const user = asyncable(fetchUser, async ($newValue, $prevValue) => {
await fetch('/user/me', {
method: 'PUT',
body: JSON.stringify($newValue)
})
});
```Every time the store is changed this `setter` or side-effect will be performed. The store may be modified selectively with `update` or completely overwritten with `set`.
```javascript
user.update($user => {
$user.visits++;
return $user;
});// or just set
user.set(user);
```As the `setter` callback receives previous value, in addition to the new, you may compare current and previous values and make a more conscious side-effect. If `setter` fails the store will automatically rollback to the previous value.
```javascript
const user = asyncable(fetchUser, async ($newValue, $prevValue) => {
if ($newValue.email !== $prevValue.email) {
throw new Error('Email cannot be modified.');
}
await saveUser($newValue);
});
```### Read-only asyncable store.
If you pass a falsy value (n.b. `undefined` excluded) as a second argument the asyncable store will be read-only.
```javascript
const tags = asyncable(fetchTags, null);tags.subscribe(async $tags => {
console.log('tags changed', await $tags); // will never triggered
});// changes won't actually be applied
tags.update($tags => {
$tags.push('new tag');
return $tags;
});
```If you pass `undefined` as a second argument to `asyncable`, the store will be writable but without `setter` side-effect. The second parameter's default value is `undefined` so it is only required in the case you need to pass a third parameter. This will be useful in case bellow.
### Dependency to another store(s).
Also, an asyncable store may depend on another store(s). Just pass an array of such stores as a third argument to `asyncable`. These values will be available to the `getter`. An asyncable may even depend on another asyncable store:
```javascript
const userPosts = asyncable(async $user => {
const user = await $user;
return fetchPostsByUser(user.id);
}, undefined, [ user ]);userPosts.subscribe(async posts => {
console.log('user posts', await posts);
});
```The `getter` will be triggered with the new values of related stores each time they change.
### Using with Svelte auto-subscriptions.
```svelte
{#await $user}
Loading user...
{:then user}
{user.firstName}
{:catch err}
User failed.
{/await}import { user } from './store.js';
```
### Simple synchronization with localStorage.
```javascript
function localStore(key, defaultValue) {
return asyncable(
() => JSON.parse(localStorage.getItem(key) || defaultValue),
val => localStorage.setItem(key, JSON.stringify(val))
);
}const todos = localStore('todos', []);
function addTodoItem(todo) {
todos.update($todos => {
$todos.push(todo);
return $todos;
});
}```
### Get synchronous value from async store:
```javascript
import { syncable } from 'svelte-asyncable';const todosSync = syncable(todos, []);
```Now you can use sync version of asyncable store in any places you don't need to have pending/fail states.
### Caching:
The ```getter``` is only run once at first subscription to the store. Subsequent `subscribe` or `get` calls simply share this value while at least one subscription is active. If all subscriptions are destroyed, the ```getter``` is rerun on next subscription.
However, if the data on which your store depends changes infrequently, you may wish for a store to persist for the lifetime of the application. In order to achieve this you may conditionally return your initial value on the absence of an existing value.
```javascript
export const pinsStore = asyncable(async () => {
const $pinstStore = await pinsStore.get();
if ($pinstStore.length > 0) return $pinstStore;
return getAllPins();
});
```## License
MIT © [PaulMaly](https://github.com/PaulMaly)