-
Notifications
You must be signed in to change notification settings - Fork 344
/
defineAsyncComponent.ts
128 lines (114 loc) · 3.32 KB
/
defineAsyncComponent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { isFunction, isObject, warn } from '../utils'
import { VueProxy } from './componentProxy'
import { AsyncComponent } from 'vue'
import {
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithProps,
} from './componentOptions'
type Component = VueProxy<any, any, any, any, any>
type ComponentOrComponentOptions =
// Component
| Component
// ComponentOptions
| ComponentOptionsWithoutProps
| ComponentOptionsWithArrayProps
| ComponentOptionsWithProps
export type AsyncComponentResolveResult<T = ComponentOrComponentOptions> =
| T
| { default: T } // es modules
export type AsyncComponentLoader = () => Promise<AsyncComponentResolveResult>
export interface AsyncComponentOptions {
loader: AsyncComponentLoader
loadingComponent?: ComponentOrComponentOptions
errorComponent?: ComponentOrComponentOptions
delay?: number
timeout?: number
suspensible?: boolean
onError?: (
error: Error,
retry: () => void,
fail: () => void,
attempts: number
) => any
}
export function defineAsyncComponent(
source: AsyncComponentLoader | AsyncComponentOptions
): AsyncComponent {
if (isFunction(source)) {
source = { loader: source }
}
const {
loader,
loadingComponent,
errorComponent,
delay = 200,
timeout, // undefined = never times out
suspensible = false, // in Vue 3 default is true
onError: userOnError,
} = source
if (__DEV__ && suspensible) {
warn(
`The suspensiblbe option for async components is not supported in Vue2. It is ignored.`
)
}
let pendingRequest: Promise<Component> | null = null
let retries = 0
const retry = () => {
retries++
pendingRequest = null
return load()
}
const load = (): Promise<ComponentOrComponentOptions> => {
let thisRequest: Promise<ComponentOrComponentOptions>
return (
pendingRequest ||
(thisRequest = pendingRequest =
loader()
.catch((err) => {
err = err instanceof Error ? err : new Error(String(err))
if (userOnError) {
return new Promise((resolve, reject) => {
const userRetry = () => resolve(retry())
const userFail = () => reject(err)
userOnError(err, userRetry, userFail, retries + 1)
})
} else {
throw err
}
})
.then((comp: any) => {
if (thisRequest !== pendingRequest && pendingRequest) {
return pendingRequest
}
if (__DEV__ && !comp) {
warn(
`Async component loader resolved to undefined. ` +
`If you are using retry(), make sure to return its return value.`
)
}
// interop module default
if (
comp &&
(comp.__esModule || comp[Symbol.toStringTag] === 'Module')
) {
comp = comp.default
}
if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
throw new Error(`Invalid async component load result: ${comp}`)
}
return comp
}))
)
}
return () => {
const component = load()
return {
component,
delay,
timeout,
error: errorComponent,
loading: loadingComponent,
}
}
}