apiAsyncComponent.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { warn, isFunction, isObject } from 'core/util'
  2. interface AsyncComponentOptions {
  3. loader: Function
  4. loadingComponent?: any
  5. errorComponent?: any
  6. delay?: number
  7. timeout?: number
  8. suspensible?: boolean
  9. onError?: (
  10. error: Error,
  11. retry: () => void,
  12. fail: () => void,
  13. attempts: number
  14. ) => any
  15. }
  16. type AsyncComponentFactory = () => {
  17. component: Promise<any>
  18. loading?: any
  19. error?: any
  20. delay?: number
  21. timeout?: number
  22. }
  23. /**
  24. * v3-compatible async component API.
  25. * @internal the type is manually declared in <root>/types/v3-define-async-component.d.ts
  26. * because it relies on existing manual types
  27. */
  28. export function defineAsyncComponent(
  29. source: (() => any) | AsyncComponentOptions
  30. ): AsyncComponentFactory {
  31. if (isFunction(source)) {
  32. source = { loader: source } as AsyncComponentOptions
  33. }
  34. const {
  35. loader,
  36. loadingComponent,
  37. errorComponent,
  38. delay = 200,
  39. timeout, // undefined = never times out
  40. suspensible = false, // in Vue 3 default is true
  41. onError: userOnError
  42. } = source
  43. if (__DEV__ && suspensible) {
  44. warn(
  45. `The suspensiblbe option for async components is not supported in Vue2. It is ignored.`
  46. )
  47. }
  48. let pendingRequest: Promise<any> | null = null
  49. let retries = 0
  50. const retry = () => {
  51. retries++
  52. pendingRequest = null
  53. return load()
  54. }
  55. const load = (): Promise<any> => {
  56. let thisRequest: Promise<any>
  57. return (
  58. pendingRequest ||
  59. (thisRequest = pendingRequest =
  60. loader()
  61. .catch(err => {
  62. err = err instanceof Error ? err : new Error(String(err))
  63. if (userOnError) {
  64. return new Promise((resolve, reject) => {
  65. const userRetry = () => resolve(retry())
  66. const userFail = () => reject(err)
  67. userOnError(err, userRetry, userFail, retries + 1)
  68. })
  69. } else {
  70. throw err
  71. }
  72. })
  73. .then((comp: any) => {
  74. if (thisRequest !== pendingRequest && pendingRequest) {
  75. return pendingRequest
  76. }
  77. if (__DEV__ && !comp) {
  78. warn(
  79. `Async component loader resolved to undefined. ` +
  80. `If you are using retry(), make sure to return its return value.`
  81. )
  82. }
  83. // interop module default
  84. if (
  85. comp &&
  86. (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
  87. ) {
  88. comp = comp.default
  89. }
  90. if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
  91. throw new Error(`Invalid async component load result: ${comp}`)
  92. }
  93. return comp
  94. }))
  95. )
  96. }
  97. return () => {
  98. const component = load()
  99. return {
  100. component,
  101. delay,
  102. timeout,
  103. error: errorComponent,
  104. loading: loadingComponent
  105. }
  106. }
  107. }