npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

svelte-asyncable

v2.2.0

Published

Super tiny, declarative, optimistic, async store for SvelteJS.

Downloads

3,856

Readme

Super tiny, declarative, optimistic, async store for SvelteJS.

NPM version NPM downloads

Features

  • Extends the Svelete store contract 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

npm i svelte-asyncable --save
yarn add svelte-asyncable

CDN: UNPKG | jsDelivr (available as window.Asyncable)

If you are not using ES6, instead of importing add

<script src="/path/to/svelte-asyncable/index.js"></script>

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.

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:

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:

// a point in time copy of the promise in the store

const 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:

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:

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.

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.

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.

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:

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.

{#await $user}
  <p>Loading user...</p>
{:then user}
  <b>{user.firstName}</b>
{:catch err}
  <mark>User failed.</mark>
{/await}

<script>
  import { user } from './store.js';
</script>

Simple synchronization with localStorage.

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:

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.

export const pinsStore = asyncable(async () => {
    const $pinstStore = await pinsStore.get();
    if ($pinstStore.length > 0) return $pinstStore;
    return getAllPins();
});

License

MIT © PaulMaly