@kentcdodds/tmp-remix-utils
Version:
This package contains simple utility functions to use with [Remix.run](https://remix.run).
113 lines (112 loc) • 2.96 kB
JavaScript
/**
* Get a hash of promises and await them all.
* Then return the same hash with the resolved values.
* @example
* export async function loader({ request }: LoaderArgs) {
* return json(
* promiseHash({
* user: getUser(request),
* posts: getPosts(request),
* })
* );
* }
* @example
* export async function loader({ request }: LoaderArgs) {
* return json(
* promiseHash({
* user: getUser(request),
* posts: promiseHash({
* list: getPosts(request),
* comments: promiseHash({
* list: getComments(request),
* likes: getLikes(request),
* }),
* }),
* })
* );
* }
*/
export async function promiseHash(hash) {
return Object.fromEntries(
await Promise.all(
Object.entries(hash).map(async ([key, promise]) => [key, await promise])
)
);
}
/**
* Used to uniquely identify a timeout
* @private
*/
let TIMEOUT = Symbol("TIMEOUT");
/**
* Attach a timeout to any promise, if the timeout resolves first ignore the
* original promise and throw an error
* @param promise The promise to attach a timeout to
* @param options The options to use
* @param options.ms The number of milliseconds to wait before timing out
* @param options.controller An AbortController to abort the original promise
* @returns The result of the promise
* @throws TimeoutError If the timeout resolves first
* @example
* try {
* let result = await timeout(
* fetch("https://example.com"),
* { ms: 100 }
* );
* } catch (error) {
* if (error instanceof TimeoutError) {
* // Handle timeout
* }
* }
* @example
* try {
* let controller = new AbortController();
* let result = await timeout(
* fetch("https://example.com", { signal: controller.signal }),
* { ms: 100, controller }
* );
* } catch (error) {
* if (error instanceof TimeoutError) {
* // Handle timeout
* }
* }
*/
export function timeout(promise, options) {
return new Promise(async (resolve, reject) => {
let timer = null;
try {
let result = await Promise.race([
promise,
new Promise((resolve) => {
timer = setTimeout(() => resolve(TIMEOUT), options.ms);
}),
]);
if (timer) clearTimeout(timer);
if (result === TIMEOUT) {
if (options.controller) options.controller.abort();
return reject(new TimeoutError(`Timed out after ${options.ms}ms`));
}
return resolve(result);
} catch (error) {
if (timer) clearTimeout(timer);
reject(error);
}
});
}
/**
* An error thrown when a timeout occurs
* @example
* try {
* let result = await timeout(fetch("https://example.com"), { ms: 100 });
* } catch (error) {
* if (error instanceof TimeoutError) {
* // Handle timeout
* }
* }
*/
export class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
}