介绍
响应异步状态,useAsyncState 不会阻塞后面的函数执行,当 promise 的状态变为成功时异步状态会自动改变。
import axios from 'axios'
import { useAsyncState } from '@vueuse/core'
const { state, isReady, isLoading } = useAsyncState(
axios
.get('https://jsonplaceholder.typicode.com/todos/1')
.then(t => t.data),
{ id: null },
)
源码
/**
* Reactive async state. Will not block your setup function and will trigger changes once
* the promise is ready.
*
* @see https://vueuse.org/useAsyncState
* @param promise The promise / async function to be resolved
* @param initialState The initial state, used until the first evaluation finishes
* @param options
*/
export function useAsyncState<Data, Params extends any[] = [], Shallow extends boolean = true>(
promise: Promise<Data> | ((...args: Params) => Promise<Data>),
initialState: Data,
options?: UseAsyncStateOptions<Shallow, Data>,
): UseAsyncStateReturn<Data, Params, Shallow> {
const {
immediate = true,
delay = 0,
onError = noop,
onSuccess = noop,
resetOnExecute = true,
shallow = true,
throwError,
} = options ?? {}
const state = shallow ? shallowRef(initialState) : ref(initialState)
const isReady = ref(false)
const isLoading = ref(false)
const error = shallowRef<unknown | undefined>(undefined)
async function execute(delay = 0, ...args: any[]) {
if (resetOnExecute)
state.value = initialState
error.value = undefined
isReady.value = false
isLoading.value = true
if (delay > 0)
await promiseTimeout(delay)
const _promise = typeof promise === 'function'
? promise(...args as Params)
: promise
try {
const data = await _promise
state.value = data
isReady.value = true
onSuccess(data)
}
catch (e) {
error.value = e
onError(e)
if (throwError)
throw e
}
finally {
isLoading.value = false
}
return state.value as Data
}
if (immediate)
execute(delay)
const shell: UseAsyncStateReturnBase<Data, Params, Shallow> = {
state: state as Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>,
isReady,
isLoading,
error,
execute,
}
function waitUntilIsLoaded() {
return new Promise<UseAsyncStateReturnBase<Data, Params, Shallow>>((resolve, reject) => {
until(isLoading).toBe(false)
.then(() => resolve(shell))
.catch(reject)
})
}
return {
...shell,
then(onFulfilled, onRejected) {
return waitUntilIsLoaded()
.then(onFulfilled, onRejected)
},
}
}
源码中 until(isLoading).toBe(false)监听 isLoading 代码。
createUntil:
function toBe<P>(value: MaybeRefOrGetter<P | T>, options?: UntilToMatchOptions) {
if (!isRef(value))
return toMatch(v => v === value, options)
const { flush = 'sync', deep = false, timeout, throwOnTimeout } = options ?? {}
let stop: Function | null = null
const watcher = new Promise<T>((resolve) => {
stop = watch(
[r, value],
([v1, v2]) => {
if (isNot !== (v1 === v2)) {
stop?.()
resolve(v1)
}
},
{
flush,
deep,
immediate: true,
},
)
})
const promises = [watcher]
if (timeout != null) {
promises.push(
promiseTimeout(timeout, throwOnTimeout)
.then(() => toValue(r))
.finally(() => {
stop?.()
return toValue(r)
}),
)
}
return Promise.race(promises)
}
要点
until(isLoading).toBe(false)
useAsyncState 返回 then 方法,类似 promise 的 then 方法,通过监听 isLoading 来触发 promise 的状态改变。
shallowRef
state 使用 shallowRef 浅层作用形式,作者希望该方法作用于展示类的异步状态,并且把业务中发送请求是的 loading 也放入其中 isLoading 方便绑定 loading。
execute
返回 execute 可以更灵活的控制请求发送的时机。
onSuccess
是在请求成功时触发的函数。
思考
使用场景
useAsyncState 适用于纯展示的异步请求,比如表格、详情。对于交互响应的场景需要对 state 进行额外处理。
封装的功能
useAsyncState 相对于平时业务代码请求来说,其封装了 state 的声明和复制以及 loading,使用的方便和精简并不是很高。
优化方向
对请求响应数据格式的处理
在业务代码中经常遇到要对请求返回的数据的格式进行处理,useAsyncState 方法是在 await 之后在对 state 进行处理,个人不是很喜欢这种方式。我还是喜欢在处理完数据格式之后赋值给 state。
请求响应状态码的处理
在业务代码中经常遇到根据请求返回的状态码来做提示或声明 state 的操作,useAsyncState 则要返回 state 之后判断其中 code 状态。
loading 更细致的处理
useAsyncState 虽然封装了 loading 但执行简单的记录请求状态,而在业务代码中往往会用一个区域包含多个请求,请求可能是串行的也可以是并行其中 loading 的记录规则并不相同。
业务中常常在用户体验上对 loading 进行延迟赋值优化,当请求响应时间比较短时 loading 就不显示,避免出现 loading 闪现的情况,只有当请求响应时间比较长的情况下才显示 loading。
useAsyncState 如果把上面 loading 的情况考虑进行是不是能好些。