diff --git a/src/core/vdom/helpers/resolve-async-component.js b/src/core/vdom/helpers/resolve-async-component.js index 241e332f..08e28a0c 100644 --- a/src/core/vdom/helpers/resolve-async-component.js +++ b/src/core/vdom/helpers/resolve-async-component.js @@ -1,62 +1,101 @@ /* @flow */ -// () => ({ -// component: import('./xxx.vue'), -// delay: 200, -// loading: LoadingComponent, -// error: ErrorComponent -// }) - import { warn, - isObject + once, + isDef, + isUndef, + isTrue, + isObject, + isFunction } from 'core/util/index' +function ensureCtor (comp, base) { + return isObject(comp) + ? base.extend(comp) + : comp +} + export function resolveAsyncComponent ( factory: Function, baseCtor: Class, context: Component ): Class | void { - if (factory.resolved) { + if (isTrue(factory.error) && isDef(factory.errorComp)) { + return factory.errorComp + } + + if (isDef(factory.resolved)) { return factory.resolved } - const cb = () => context.$forceUpdate() - if (factory.requested) { - // pool callbacks - factory.pendingCallbacks.push(cb) + if (isTrue(factory.loading) && isDef(factory.loadingComp)) { + return factory.loadingComp + } + + if (isDef(factory.contexts)) { + // already pending + factory.contexts.push(context) } else { - factory.requested = true - const cbs = factory.pendingCallbacks = [cb] + const contexts = factory.contexts = [context] let sync = true - const resolve = (res: Object | Class) => { - if (isObject(res)) { - res = baseCtor.extend(res) - } - // cache resolved - factory.resolved = res - // invoke callbacks only if this is not a synchronous resolve - // (async resolves are shimmed as synchronous during SSR) - if (!sync) { - for (let i = 0, l = cbs.length; i < l; i++) { - cbs[i](res) - } + const forceRender = () => { + for (let i = 0, l = contexts.length; i < l; i++) { + contexts[i].$forceUpdate() } } - const reject = reason => { + const resolve = once((res: Object | Class) => { + // cache resolved + factory.resolved = ensureCtor(res, baseCtor) + // invoke callbacks only if this is not a synchronous resolve + // (async resolves are shimmed as synchronous during SSR) + if (!sync) { + forceRender() + } + }) + + const reject = once(reason => { process.env.NODE_ENV !== 'production' && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) - } + if (isDef(factory.errorComp)) { + factory.error = true + forceRender() + } + }) const res = factory(resolve, reject) - // handle promise - if (res && typeof res.then === 'function' && !factory.resolved) { - res.then(resolve, reject) + if (isObject(res)) { + if (isFunction(res.then)) { + // () => Promise + if (isUndef(factory.resolved)) { + res.then(resolve, reject) + } + } else if (isDef(res.component) && isFunction(res.component.then)) { + if (isDef(res.error)) { + factory.errorComp = ensureCtor(res.error, baseCtor) + } + + if (isDef(res.loading)) { + factory.loadingComp = ensureCtor(res.loading, baseCtor) + setTimeout(() => { + if (isUndef(factory.resolved) && isUndef(factory.error)) { + factory.loading = true + forceRender() + } + }, res.delay || 200) + } + + if (isDef(res.timeout)) { + setTimeout(reject, res.timeout) + } + + res.component.then(resolve, reject) + } } sync = false diff --git a/src/shared/util.js b/src/shared/util.js index e88473e9..585ae9fe 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -14,6 +14,10 @@ export function isTrue (v: any): boolean { return v === true } +export function isFunction (v: any): boolean { + return typeof v === 'function' +} + /** * Convert a value to a string that is actually rendered. */ @@ -250,10 +254,10 @@ export function looseIndexOf (arr: Array, val: mixed): number { */ export function once (fn: Function): Function { let called = false - return () => { + return function () { if (!called) { called = true - fn() + fn.apply(this, arguments) } } }