Skip to content

Instantly share code, notes, and snippets.

@kossnocorp
Last active April 28, 2022 04:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kossnocorp/e9f8e738c21a0d605236167d8a55495d to your computer and use it in GitHub Desktop.
Save kossnocorp/e9f8e738c21a0d605236167d8a55495d to your computer and use it in GitHub Desktop.
Promise hooks
import { useEffect, useMemo, useRef } from 'preact/hooks'
/**
* The {@link usePromise} resolve function.
*/
export type UsePromiseResolve<Type> = (value: Type) => void
/**
* The {@link usePromise} reject function.
*/
export type UsePromiseReject = (error: unknown) => void
/**
* The {@link usePromise} hook result.
*/
export interface UsePromiseResult<Type> {
/** The promise to the value of given type */
promise: Promise<Type>
/** The function that resolve the promise with the given value */
resolve: UsePromiseResolve<Type>
/** The function that rejects the promise with the given reason */
reject: UsePromiseReject
}
/**
* A hook that creates promise components - the promise itself, and resolve and
* reject functions that can be used to fulfill the promise.
*
* @returns promise components
*/
export function usePromise<Type>(): UsePromiseResult<Type> {
const valueRef = useRef<Type>()
const errorRef = useRef<unknown>()
const resolveRef = useRef<UsePromiseResolve<Type>>()
const rejectRef = useRef<UsePromiseReject>()
const promise = useMemo(
() =>
new Promise<Type>((promiseResolve, promiseReject) => {
if (valueRef.current) {
// If the value is already there, resolve it
promiseResolve(valueRef.current)
} else {
// Otherwise, save the resolve function
resolveRef.current = promiseResolve
}
if (errorRef.current) {
// If the error is already there, reject it
promiseReject(errorRef.current)
} else {
// Otherwise, save the reject function
rejectRef.current = promiseReject
}
}),
[]
)
const resolve: UsePromiseResolve<Type> = useMemo(
() => (value) => {
if (resolveRef.current) {
// If the resolve function is already there, use it
resolveRef.current(value)
} else {
// Otherwise, save the value
valueRef.current = value
}
},
[]
)
const reject: UsePromiseReject = useMemo(
() => (error) => {
if (rejectRef.current) {
// If the reject function is already there, use it
rejectRef.current(error)
} else {
// Otherwise, save the error
errorRef.current = error
}
},
[]
)
return { promise, resolve, reject }
}
/**
* The {@link usePromiseFrom} hook result.
*/
export interface UsePromiseFromResult<Type> {
/** The promise to the value of given type */
promise: Promise<Type>
/** The value if the promise is resolved */
current: Type | undefined | null
/** The error if the promise is reject */
error: Error | undefined
}
/**
* A hook that creates promise from the given value. If the value is defined and
* not an error, the promise will resolve and the hook will have the value
* defined. If the value is an error, the promise will reject and the hook will
* have the error defined.
*
* @param value - the value to create promise from
* @returns the promise to the value as well as the value or error
*/
export function usePromiseFrom<Type>(
value: Type | Error | undefined | null
): UsePromiseFromResult<Type> {
const { promise, resolve, reject } = usePromise<Type>()
useEffect(() => {
if (!value) return
if (value instanceof Error) {
reject(value)
} else {
// We can safely call the resolve function multiple times as only
// he first value will be resolved
resolve(value)
}
}, [value])
const isError = value instanceof Error
// Exclude the error from the value, so the value is either of the given
// type or undefined
const current = isError ? undefined : value
// Set error only if the value is an error
const error = isError ? value : undefined
return { promise, current, error }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment