2021-09-30 18:19:55 +00:00
|
|
|
import { onBeforeMount, onUnmounted, ref, unref } from 'vue'
|
2021-08-09 16:18:21 +00:00
|
|
|
import type { UnwrapRef, Ref } from 'vue'
|
2021-03-17 09:17:18 +00:00
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
import { ensureReactive, useGlobalData } from './data'
|
2021-08-27 13:30:53 +00:00
|
|
|
import { NuxtApp, useNuxtApp } from '#app'
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2021-08-27 13:30:53 +00:00
|
|
|
export type AsyncDataFn<T> = (ctx?: NuxtApp) => Promise<T>
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
export interface AsyncDataOptions {
|
2021-01-18 12:46:19 +00:00
|
|
|
server?: boolean
|
|
|
|
defer?: boolean
|
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
export interface AsyncDataState<T> {
|
|
|
|
data: UnwrapRef<T>
|
2021-02-03 18:14:30 +00:00
|
|
|
pending: Ref<boolean>
|
2021-04-03 10:03:20 +00:00
|
|
|
fetch: (force?: boolean) => Promise<UnwrapRef<T>>
|
2021-01-18 12:46:19 +00:00
|
|
|
error?: any
|
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
export type AsyncDataResult<T> = AsyncDataState<T> & Promise<AsyncDataState<T>>
|
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
export function useAsyncData (defaults?: AsyncDataOptions) {
|
2021-08-27 13:30:53 +00:00
|
|
|
const nuxt = useNuxtApp()
|
2021-04-03 10:03:20 +00:00
|
|
|
const onBeforeMountCbs: Array<() => void> = []
|
2021-01-18 12:46:19 +00:00
|
|
|
|
|
|
|
if (process.client) {
|
2021-04-03 10:03:20 +00:00
|
|
|
onBeforeMount(() => {
|
|
|
|
onBeforeMountCbs.forEach((cb) => { cb() })
|
|
|
|
onBeforeMountCbs.splice(0, onBeforeMountCbs.length)
|
2021-01-18 12:46:19 +00:00
|
|
|
})
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
onUnmounted(() => onBeforeMountCbs.splice(0, onBeforeMountCbs.length))
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
nuxt._asyncDataPromises = nuxt._asyncDataPromises || {}
|
|
|
|
|
|
|
|
return function asyncData<T extends Record<string, any>> (
|
|
|
|
key: string,
|
2021-03-17 09:17:18 +00:00
|
|
|
handler: AsyncDataFn<T>,
|
2021-04-03 10:03:20 +00:00
|
|
|
options: AsyncDataOptions = {}
|
|
|
|
): AsyncDataResult<T> {
|
2021-02-03 18:14:30 +00:00
|
|
|
if (typeof handler !== 'function') {
|
|
|
|
throw new TypeError('asyncData handler must be a function')
|
|
|
|
}
|
2021-01-18 12:46:19 +00:00
|
|
|
options = {
|
|
|
|
server: true,
|
|
|
|
defer: false,
|
|
|
|
...defaults,
|
|
|
|
...options
|
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
const globalData = useGlobalData(nuxt)
|
2021-03-17 09:17:18 +00:00
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
const state = {
|
|
|
|
data: ensureReactive(globalData, key) as UnwrapRef<T>,
|
|
|
|
pending: ref(true)
|
|
|
|
} as AsyncDataState<T>
|
2021-03-17 09:17:18 +00:00
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
const fetch = (force?: boolean): Promise<UnwrapRef<T>> => {
|
|
|
|
if (nuxt._asyncDataPromises[key] && !force) {
|
|
|
|
return nuxt._asyncDataPromises[key]
|
|
|
|
}
|
|
|
|
state.pending.value = true
|
|
|
|
nuxt._asyncDataPromises[key] = Promise.resolve(handler(nuxt)).then((result) => {
|
2021-03-17 09:17:18 +00:00
|
|
|
for (const _key in result) {
|
2021-09-30 18:19:55 +00:00
|
|
|
// @ts-expect-error
|
2021-04-03 10:03:20 +00:00
|
|
|
state.data[_key] = unref(result[_key])
|
2021-03-01 09:45:37 +00:00
|
|
|
}
|
2021-04-03 10:03:20 +00:00
|
|
|
return state.data
|
|
|
|
}).finally(() => {
|
|
|
|
state.pending.value = false
|
|
|
|
nuxt._asyncDataPromises[key] = null
|
|
|
|
})
|
|
|
|
return nuxt._asyncDataPromises[key]
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
const fetchOnServer = options.server !== false
|
2021-01-18 12:46:19 +00:00
|
|
|
const clientOnly = options.server === false
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
// Server side
|
|
|
|
if (process.server && fetchOnServer) {
|
|
|
|
fetch()
|
|
|
|
}
|
|
|
|
|
2021-01-18 12:46:19 +00:00
|
|
|
// Client side
|
|
|
|
if (process.client) {
|
|
|
|
// 1. Hydration (server: true): no fetch
|
2021-04-03 10:03:20 +00:00
|
|
|
if (nuxt.isHydrating && fetchOnServer) {
|
|
|
|
state.pending.value = false
|
2021-02-03 18:14:30 +00:00
|
|
|
}
|
2021-01-18 12:46:19 +00:00
|
|
|
// 2. Initial load (server: false): fetch on mounted
|
2021-04-03 10:03:20 +00:00
|
|
|
if (nuxt.isHydrating && clientOnly) {
|
2021-01-18 12:46:19 +00:00
|
|
|
// Fetch on mounted (initial load or deferred fetch)
|
2021-04-03 10:03:20 +00:00
|
|
|
onBeforeMountCbs.push(fetch)
|
|
|
|
} else if (!nuxt.isHydrating) { // Navigation
|
2021-01-18 12:46:19 +00:00
|
|
|
if (options.defer) {
|
|
|
|
// 3. Navigation (defer: true): fetch on mounted
|
2021-04-03 10:03:20 +00:00
|
|
|
onBeforeMountCbs.push(fetch)
|
2021-01-18 12:46:19 +00:00
|
|
|
} else {
|
|
|
|
// 4. Navigation (defer: false): await fetch
|
2021-04-03 10:03:20 +00:00
|
|
|
fetch()
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
const res = Promise.resolve(nuxt._asyncDataPromises[key]).then(() => state) as AsyncDataResult<T>
|
|
|
|
res.data = state.data
|
|
|
|
res.pending = state.pending
|
|
|
|
res.fetch = fetch
|
|
|
|
return res
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-01 09:45:37 +00:00
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
export function asyncData<T extends Record<string, any>> (
|
|
|
|
key: string, handler: AsyncDataFn<T>, options?: AsyncDataOptions
|
|
|
|
): AsyncDataResult<T> {
|
|
|
|
return useAsyncData()(key, handler, options)
|
2021-03-01 09:45:37 +00:00
|
|
|
}
|