2021-02-03 18:14:30 +00:00
|
|
|
import { Ref, ref, toRef, onMounted, watch, getCurrentInstance, onUnmounted } from 'vue'
|
2021-01-18 12:46:19 +00:00
|
|
|
import { Nuxt, useNuxt } from 'nuxt/app'
|
|
|
|
import { useData } from './data'
|
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
export type AsyncDataFn<T> = (ctx?: Nuxt) => 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-02-03 18:14:30 +00:00
|
|
|
export interface AsyncDataObj<T> {
|
2021-01-18 12:46:19 +00:00
|
|
|
data: Ref<T>
|
2021-02-03 18:14:30 +00:00
|
|
|
pending: Ref<boolean>
|
|
|
|
refresh: Function
|
2021-01-18 12:46:19 +00:00
|
|
|
error?: any
|
|
|
|
}
|
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
export function useAsyncData (defaults?: AsyncDataOptions) {
|
2021-01-18 12:46:19 +00:00
|
|
|
const nuxt = useNuxt()
|
|
|
|
const vm = getCurrentInstance()
|
|
|
|
|
|
|
|
let data = useData(nuxt, vm)
|
|
|
|
let dataRef = 1
|
|
|
|
|
|
|
|
const onMountedCbs = []
|
|
|
|
|
|
|
|
if (process.client) {
|
|
|
|
onMounted(() => {
|
|
|
|
onMountedCbs.forEach((cb) => { cb() })
|
|
|
|
onMountedCbs.splice(0, onMountedCbs.length)
|
|
|
|
})
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
onMountedCbs.splice(0, onMountedCbs.length)
|
|
|
|
data = null
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
return async function asyncData<T = any> (handler: AsyncDataFn<T>, options?: AsyncDataOptions): Promise<AsyncDataObj<T>> {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
const key = String(dataRef++)
|
2021-02-03 18:14:30 +00:00
|
|
|
const pending = ref(true)
|
2021-01-18 12:46:19 +00:00
|
|
|
|
|
|
|
const fetch = async () => {
|
2021-02-03 18:14:30 +00:00
|
|
|
pending.value = true
|
|
|
|
const _handler = handler(nuxt)
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
if (_handler instanceof Promise) {
|
2021-01-18 12:46:19 +00:00
|
|
|
// Let user resolve if request is promise
|
2021-02-03 18:14:30 +00:00
|
|
|
// TODO: handle error
|
|
|
|
data[key] = await _handler
|
|
|
|
pending.value = false
|
2021-01-18 12:46:19 +00:00
|
|
|
} else {
|
|
|
|
// Invalid request
|
2021-02-03 18:14:30 +00:00
|
|
|
throw new TypeError('Invalid asyncData handler: ' + _handler)
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const clientOnly = options.server === false
|
|
|
|
|
|
|
|
// Client side
|
|
|
|
if (process.client) {
|
|
|
|
// 1. Hydration (server: true): no fetch
|
2021-02-03 18:14:30 +00:00
|
|
|
if (nuxt.isHydrating && options.server) {
|
|
|
|
pending.value = false
|
|
|
|
}
|
2021-01-18 12:46:19 +00:00
|
|
|
// 2. Initial load (server: false): fetch on mounted
|
|
|
|
if (nuxt.isHydrating && !options.server) {
|
|
|
|
// Fetch on mounted (initial load or deferred fetch)
|
|
|
|
onMountedCbs.push(fetch)
|
|
|
|
} else if (!nuxt.isHydrating) {
|
|
|
|
if (options.defer) {
|
|
|
|
// 3. Navigation (defer: true): fetch on mounted
|
|
|
|
onMountedCbs.push(fetch)
|
|
|
|
} else {
|
|
|
|
// 4. Navigation (defer: false): await fetch
|
|
|
|
await fetch()
|
|
|
|
}
|
|
|
|
}
|
2021-02-03 18:14:30 +00:00
|
|
|
// Watch handler
|
|
|
|
watch(handler.bind(null, nuxt), fetch)
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Server side
|
|
|
|
if (process.server && !clientOnly) {
|
|
|
|
await fetch()
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
data: toRef<any, string>(data, key),
|
2021-02-03 18:14:30 +00:00
|
|
|
pending,
|
|
|
|
refresh: fetch
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|