svelte-asyncable
Version:
Super tiny, declarative, optimistic, async store for SvelteJS.
199 lines (189 loc) • 5.26 kB
JavaScript
function noop() { }
function run(fn) {
return fn();
}
function run_all(fns) {
fns.forEach(run);
}
function is_function(thing) {
return typeof thing === 'function';
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function subscribe(store, ...callbacks) {
if (store == null) {
return noop;
}
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function get_store_value(store) {
let value;
subscribe(store, _ => value = _)();
return value;
}
const subscriber_queue = [];
/**
* Creates a `Readable` store that allows reading by subscription.
* @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions
*/
function readable(value, start) {
return {
subscribe: writable(value, start).subscribe
};
}
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
function writable(value, start = noop) {
let stop;
const subscribers = new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(value));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
function derived(stores, fn, initial_value) {
const single = !Array.isArray(stores);
const stores_array = single
? [stores]
: stores;
const auto = fn.length < 2;
return readable(initial_value, (set) => {
let inited = false;
const values = [];
let pending = 0;
let cleanup = noop;
const sync = () => {
if (pending) {
return;
}
cleanup();
const result = fn(single ? values[0] : values, set);
if (auto) {
set(result);
}
else {
cleanup = is_function(result) ? result : noop;
}
};
const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => {
values[i] = value;
pending &= ~(1 << i);
if (inited) {
sync();
}
}, () => {
pending |= (1 << i);
}));
inited = true;
sync();
return function stop() {
run_all(unsubscribers);
cleanup();
};
});
}
function asyncable(getter, setter = () => {}, stores = []) {
let resolve;
const initial = new Promise((res) => (resolve = res));
const derived$ = derived(stores, (values) => values);
const store$ = writable(initial, (set) => {
return derived$.subscribe(async (values = []) => {
let value = getter(...values);
if (value === undefined) return;
value = Promise.resolve(value);
set(value);
resolve(value);
});
});
async function set(newValue, oldValue) {
if (newValue === oldValue) return;
store$.set(Promise.resolve(newValue));
try {
await setter(newValue, oldValue);
} catch (err) {
store$.set(Promise.resolve(oldValue));
throw err;
}
}
return {
subscribe: store$.subscribe,
async update(reducer) {
if (!setter) return;
let oldValue;
let newValue;
try {
oldValue = await get_store_value(store$);
newValue = await reducer(shallowCopy(oldValue));
} finally {
await set(newValue, oldValue);
}
},
async set(newValue) {
if (!setter) return;
let oldValue;
try {
oldValue = await get_store_value(store$);
newValue = await newValue;
} finally {
await set(newValue, oldValue);
}
},
get() {
return get_store_value(store$);
},
};
}
function syncable(stores, initialValue) {
return derived(
stores,
($values, set) =>
(Array.isArray(stores) ? Promise.allSettled : Promise.resolve)
.call(Promise, $values)
.then(set),
initialValue
);
}
function shallowCopy(value) {
if (typeof value !== 'object' || value === null) return value;
return Array.isArray(value) ? [...value] : { ...value };
}
export { asyncable, syncable };