Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 7x 7x 7x 7x 7x 5x 7x 7x 7x 7x 7x 5x 5x 7x 1x 1x 1x 7x 7x 6x 6x 7x 7x 7x 5x 5x 5x 5x 5x 5x | import { createSignal } from './signal';
import { createEffect } from './effect';
import { createMemo } from './memo';
import { onCleanup } from '../lifecycle/lifecycle';
import type { Resource, SignalGetter, ReactiveOptions } from '../types';
/**
* Information passed to the fetcher function, including an AbortSignal
* to cancel the request if the resource is disposed or re-fetched.
*/
export interface FetcherInfo {
signal: AbortSignal;
}
type Fetcher<S, T> = (source: S, info?: FetcherInfo) => T | Promise<T>;
/**
* Creates a resource that handles asynchronous data fetching with reactive state management.
*
* Resources automatically track loading, error, and data states. They re-fetch data
* whenever their source signal changes and provide reactive access to all states.
*
* @param source A signal that provides the input for the fetcher function
* @param fetcher A function that takes the source value and returns data or a Promise
* @param options Optional configuration including debug name
* @returns A resource object with reactive loading, error, and data states
*
* @example
* ```typescript
* const [userId, setUserId] = createSignal(1);
*
* const userResource = createResource(userId, async (id) => {
* const response = await fetch(`/api/users/${id}`);
* return response.json();
* });
*
* // Access reactive states
* console.log(userResource.loading()); // true while fetching
* console.log(userResource.error()); // Error object if fetch failed
* console.log(userResource()); // User data when available
*
* // Change source triggers automatic re-fetch
* setUserId(2); // Automatically fetches user with ID 2
*
* // Handle different states in effects
* createEffect(() => {
* if (userResource.loading()) {
* showLoadingSpinner();
* } else if (userResource.error()) {
* showError(userResource.error());
* } else {
* displayUser(userResource());
* }
* });
* ```
*/
export function createResource<S, T, E = unknown>(
source: SignalGetter<S>,
fetcher: Fetcher<S, T>,
options?: ReactiveOptions
): Resource<T, E> {
const [data, setData] = createSignal<T | undefined>(undefined, options);
const [loading, setLoading] = createSignal<boolean>(true);
const [error, setError] = createSignal<E | undefined>(undefined);
createEffect(() => {
const sourceValue = source();
setLoading(true);
setError(undefined);
const controller = new AbortController();
// Abort the fetch if the effect re-runs or the reactive scope is disposed
onCleanup(() => {
controller.abort();
});
const executeFetch = async () => {
try {
const result = await fetcher(sourceValue, { signal: controller.signal });
if (!controller.signal.aborted) {
setData(result);
}
} catch (e) {
if (!controller.signal.aborted) {
setError(e as E);
}
} finally {
if (!controller.signal.aborted) {
setLoading(false);
}
}
};
executeFetch();
}, options);
// The main return value is a memo of the data
const resourceMemo = createMemo(() => data(), options);
// Attach the loading and error states as memos
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(resourceMemo as any).loading = createMemo(() => loading());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(resourceMemo as any).error = createMemo(() => error());
return resourceMemo as Resource<T, E>;
}
|