Skip to content

Instantly share code, notes, and snippets.

@aiyogg
Created April 9, 2024 03:23
Show Gist options
  • Save aiyogg/3ce249f7ec10f13b10ccad671bf2e1ab to your computer and use it in GitHub Desktop.
Save aiyogg/3ce249f7ec10f13b10ccad671bf2e1ab to your computer and use it in GitHub Desktop.
React use Promise
import React from "react"
import type { ReactNode } from "react"
const { createContext, useContext, useRef, useState } = React;
type PromiseCanUse<T> = Promise<T> & {
status?: 'pending' | 'fulfilled' | 'rejected'
reason?: unknown
value?: T
}
/**
* 在当前组件使用 loading/error
*/
export function usePromise<T>(promise?: PromiseCanUse<T>) {
const [_, forceUpdate] = useState({})
const ref = useRef<PromiseCanUse<T>>()
if (!promise) return { loading: false, data: promise }
ref.current = promise
if (!promise.status) {
promise.status = 'pending'
promise
.then(
(result) => {
promise.status = 'fulfilled'
promise.value = result
},
(reason) => {
promise.status = 'rejected'
promise.reason = reason
}
)
.finally(() => {
setTimeout(() => {
if (ref.current === promise) {
forceUpdate({})
}
}, 0)
})
}
return {
loading: promise.status === 'pending',
data: promise.value,
error: promise.reason,
}
}
/**
* 在父级/祖父级组件中使用 Suspense/ErrorBoundary 接收 loading/error
*/
export function use<T>(promise?: PromiseCanUse<T>) {
if (!promise) return promise
if (promise.status === 'fulfilled') {
return promise.value
} else if (promise.status === 'rejected') {
throw promise.reason
} else if (promise.status === 'pending') {
throw promise
} else {
promise.status = 'pending'
promise.then(
(result) => {
promise.status = 'fulfilled'
promise.value = result
},
(reason) => {
promise.status = 'rejected'
promise.reason = reason
}
)
throw promise
}
}
const AsyncDataContext = createContext<unknown>(undefined)
/**
* 在当前组件或父级/祖父级组件中使用 Suspense/ErrorBoundary 接收 loading/error
*/
export const Await = <T,>(props: {
resolver?: Promise<T>
children?: ReactNode | undefined | ((data?: T) => ReactNode)
}) => {
const { resolver, children } = props
const data = use(resolver)
if (typeof children === 'function') {
return children(data)
}
return (
<AsyncDataContext.Provider value={data}>
{children}
</AsyncDataContext.Provider>
)
}
/**
* 在当前组件接收来自父级 <Await /> 组件的 data
* @deprecated 不推荐使用, 会丢失 ts 类型
*/
export const useAsyncValue = () => {
return useContext(AsyncDataContext)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment