nextTick,
onMounted,
watch,
- onUnmounted
+ onUnmounted,
+ onErrorCaptured
} from '@vue/runtime-test'
describe('renderer: suspense', () => {
expect(calls).toEqual([`inner mounted`, `outer mounted`])
})
- test.todo('error handling')
+ test('error handling', async () => {
+ const Async = {
+ async setup() {
+ throw new Error('oops')
+ }
+ }
+
+ const Comp = {
+ setup() {
+ const error = ref<any>(null)
+ onErrorCaptured(e => {
+ error.value = e
+ return true
+ })
+
+ return () =>
+ error.value
+ ? h('div', error.value.message)
+ : h(Suspense, null, {
+ default: h(Async),
+ fallback: h('div', 'fallback')
+ })
+ }
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ expect(serializeInner(root)).toBe(`<div>fallback</div>`)
+
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(`<div>oops</div>`)
+ })
test.todo('new async dep after resolve should cause suspense to restart')
createSuspenseBoundary,
normalizeSuspenseChildren
} from './suspense'
+import { handleError, ErrorCodes } from './errorHandling'
const prodEffectOptions = {
scheduler: queueJob
if (__DEV__) {
pushWarningContext(n2)
}
- updateComponentPropsAndSlots(instance, n2)
+ updateComponentPreRender(instance, n2)
if (__DEV__) {
popWarningContext()
}
// state again
}
parentSuspense.deps++
- instance.asyncDep.then(asyncSetupResult => {
- // unmounted before resolve
- if (instance.isUnmounted || parentSuspense.isUnmounted) {
- return
- }
- parentSuspense.deps--
- // retry from this component
- instance.asyncResolved = true
- handleSetupResult(instance, asyncSetupResult, parentSuspense)
- setupRenderEffect(
- instance,
- parentSuspense,
- initialVNode,
- container,
- anchor,
- isSVG
- )
- updateHOCHostEl(instance, initialVNode.el as HostNode)
- if (parentSuspense.deps === 0) {
- resolveSuspense(parentSuspense)
- }
- })
+ instance.asyncDep
+ .catch(err => {
+ handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
+ })
+ .then(asyncSetupResult => {
+ // component may be unmounted before resolve
+ if (!instance.isUnmounted && !parentSuspense.isUnmounted) {
+ retryAsyncComponent(
+ instance,
+ asyncSetupResult,
+ parentSuspense,
+ isSVG
+ )
+ }
+ })
// give it a placeholder
const placeholder = (instance.subTree = createVNode(Empty))
processEmptyNode(null, placeholder, container, anchor)
}
}
+ function retryAsyncComponent(
+ instance: ComponentInternalInstance,
+ asyncSetupResult: unknown,
+ parentSuspense: HostSuspsenseBoundary,
+ isSVG: boolean
+ ) {
+ parentSuspense.deps--
+ // retry from this component
+ instance.asyncResolved = true
+ const { vnode } = instance
+ if (__DEV__) {
+ pushWarningContext(vnode)
+ }
+ handleSetupResult(instance, asyncSetupResult, parentSuspense)
+ setupRenderEffect(
+ instance,
+ parentSuspense,
+ vnode,
+ // component may have been moved before resolve
+ hostParentNode(instance.subTree.el) as HostElement,
+ getNextHostNode(instance.subTree),
+ isSVG
+ )
+ updateHOCHostEl(instance, vnode.el as HostNode)
+ if (__DEV__) {
+ popWarningContext()
+ }
+ if (parentSuspense.deps === 0) {
+ resolveSuspense(parentSuspense)
+ }
+ }
+
function setupRenderEffect(
instance: ComponentInternalInstance,
parentSuspense: HostSuspsenseBoundary | null,
}
if (next !== null) {
- updateComponentPropsAndSlots(instance, next)
+ updateComponentPreRender(instance, next)
}
const prevTree = instance.subTree
const nextTree = (instance.subTree = renderComponentRoot(instance))
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
- function updateComponentPropsAndSlots(
+ function updateComponentPreRender(
instance: ComponentInternalInstance,
nextVNode: HostVNode
) {
}
}
- function getNextHostNode(vnode: HostVNode): HostNode | null {
- return vnode.component === null
- ? hostNextSibling((vnode.anchor || vnode.el) as HostNode)
- : getNextHostNode(vnode.component.subTree)
+ function getNextHostNode({
+ component,
+ suspense,
+ anchor,
+ el
+ }: HostVNode): HostNode | null {
+ if (component !== null) {
+ return getNextHostNode(component.subTree)
+ }
+ if (__FEATURE_SUSPENSE__ && suspense !== null) {
+ return getNextHostNode(
+ suspense.isResolved ? suspense.subTree : suspense.fallbackTree
+ )
+ }
+ return hostNextSibling((anchor || el) as HostNode)
}
function setRef(