jotai-eager
Version:
Jōtai utilities that help with asynchronous atoms
262 lines (252 loc) • 6.7 kB
JavaScript
;
const vanilla = require('jotai/vanilla');
const PENDING = { status: "pending" };
const promiseMetaCache = /* @__PURE__ */ new WeakMap();
function isPromiseLike(value) {
return typeof value?.then === "function";
}
function getPromiseMeta(promise) {
if (!isPromiseLike(promise)) {
return void 0;
}
return promiseMetaCache.get(promise) ?? PENDING;
}
function setPromiseMeta(promise, meta) {
promiseMetaCache.set(promise, meta);
}
function isKnown(value) {
const meta = getPromiseMeta(value);
if (meta) {
return meta.status === "fulfilled";
}
return true;
}
function getFulfilledValue(promiseOrValue) {
const meta = getPromiseMeta(promiseOrValue);
if (meta) {
return meta.value;
}
return promiseOrValue;
}
function soon(first, second) {
if (second) {
return _soonImpl(first, second);
}
return (data) => {
return _soonImpl(data, first);
};
}
function _soonImpl(data, process) {
const meta = getPromiseMeta(data);
if (meta) {
if (meta.status === "fulfilled") {
return process(meta.value);
}
if (meta.status === "rejected") {
return Promise.reject(meta.reason);
}
const promise = data;
const transformedPromise = promise.then(
(value) => {
setPromiseMeta(promise, { status: "fulfilled", value });
return process(value);
},
(reason) => {
setPromiseMeta(promise, { status: "rejected", reason });
throw reason;
}
);
return transformedPromise;
}
try {
return process(data);
} catch (err) {
return Promise.reject(err);
}
}
function soonAll(values) {
if (values.every(isKnown)) {
return values.map((el) => getFulfilledValue(el));
}
return Promise.all(values).then((fulfilledValues) => {
fulfilledValues.map((fulfilled, idx) => {
const promise = values[idx];
if (isPromiseLike(promise)) {
setPromiseMeta(promise, { status: "fulfilled", value: fulfilled });
}
});
return fulfilledValues;
});
}
function derive(deps, op) {
return vanilla.atom((get) => {
try {
return soon(
soonAll(deps.map(get)),
(values) => op(...values)
);
} catch (err) {
return Promise.reject(err);
}
});
}
const NotYet = Symbol(
"(jotai-eager) Not all dependencies were fulfilled. Are you a dev? Call `isEagerError(e)` to detect this thrown value and rethrow it, as its handled by the library."
);
function unwrapPromise(promise) {
const meta = getPromiseMeta(promise);
if (!meta) {
return promise;
}
if (meta.status === "pending") {
throw { [NotYet]: promise };
}
if (meta.status === "rejected") {
throw meta.reason;
}
return meta.value;
}
function resolveSuspension(compute, signal) {
try {
return compute();
} catch (e) {
const suspended = e[NotYet];
if (suspended) {
return suspended.then(
(value) => {
setPromiseMeta(suspended, { status: "fulfilled", value });
if (signal.aborted) {
return void 0;
}
return resolveSuspension(compute, signal);
},
(reason) => {
if (signal.aborted) {
return void 0;
}
setPromiseMeta(suspended, { status: "rejected", reason });
throw reason;
}
);
}
return Promise.reject(e);
}
}
function eagerAtom(...args) {
const [read, write] = args;
return vanilla.atom(
(get, { signal }) => {
const eagerGet = ((atomToGet) => unwrapPromise(get(atomToGet)));
eagerGet.all = (atoms) => atoms.map((a) => get(a)).map((v) => unwrapPromise(v));
eagerGet.await = (promiseOrValue) => unwrapPromise(promiseOrValue);
eagerGet.awaitAll = (values) => values.map((v) => unwrapPromise(v));
return resolveSuspension(() => read(eagerGet), signal);
},
write ?? (() => void 0)
);
}
function isEagerError(error) {
return !!error?.[NotYet];
}
const defaultFallback = () => void 0;
function withPending(anAtom, fallback = defaultFallback) {
const refreshAtom = vanilla.atom(0);
if (undefined?.MODE !== "production") {
refreshAtom.debugPrivate = true;
}
const promiseAndValueAtom = vanilla.atom(
(get, { setSelf }) => {
get(refreshAtom);
const prev = get(promiseAndValueAtom);
const promise = get(anAtom);
if (!isPromiseLike(promise)) {
return { v: promise };
}
const meta = getPromiseMeta(promise);
if (meta?.status === "fulfilled") {
return { p: promise, v: meta.value };
}
if (meta?.status === "rejected") {
throw meta.reason;
}
if (promise !== prev?.p) {
promise.then(
(value) => {
setPromiseMeta(promise, { status: "fulfilled", value });
setSelf();
},
(reason) => {
setPromiseMeta(promise, { status: "rejected", reason });
setSelf();
}
);
}
if (prev && "v" in prev) {
return {
p: promise,
f: fallback({
get,
prev: prev.v,
pending: promise
}),
v: prev.v
};
}
return {
p: promise,
f: fallback({
get,
prev: void 0,
pending: promise
})
};
},
(_get, set) => {
set(refreshAtom, (c) => c + 1);
}
);
promiseAndValueAtom.init = void 0;
if (undefined?.MODE !== "production") {
promiseAndValueAtom.debugPrivate = true;
}
return vanilla.atom(
(get) => {
const state = get(promiseAndValueAtom);
if ("f" in state) {
return state.f;
}
return state.v;
},
(_get, set, ...args) => set(anAtom, ...args)
);
}
const cache1 = /* @__PURE__ */ new WeakMap();
const memo1 = (create, dep1) => (cache1.has(dep1) ? cache1 : cache1.set(dep1, create())).get(dep1);
const Pending = Symbol("The loadable is pending");
function loadable(anAtom) {
return memo1(() => {
const atomWithPending = withPending(anAtom, () => Pending);
if (undefined?.MODE !== "production") {
atomWithPending.debugPrivate = true;
}
return vanilla.atom((get) => {
let value;
try {
value = get(atomWithPending);
} catch (error) {
return { state: "hasError", error };
}
if (value === Pending) {
return { state: "loading" };
}
return { state: "hasData", data: value };
});
}, anAtom);
}
exports.derive = derive;
exports.eagerAtom = eagerAtom;
exports.isEagerError = isEagerError;
exports.loadable = loadable;
exports.soon = soon;
exports.soonAll = soonAll;
exports.withPending = withPending;