diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index 4228bc8401c..421bc0a8e96 100644
--- a/packages/runtime-core/__tests__/components/Suspense.spec.ts
+++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts
@@ -69,6 +69,70 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`
async
`)
})
+ test('emits events', async () => {
+ const Async = defineAsyncComponent({
+ render() {
+ return h('div', 'async')
+ }
+ })
+
+ const onFallback = jest.fn()
+ const onResolve = jest.fn()
+ const onPending = jest.fn()
+
+ const show = ref(true)
+ const Comp = {
+ setup() {
+ return () =>
+ h(
+ Suspense,
+ {
+ onFallback,
+ onResolve,
+ onPending,
+ // force displaying the fallback right away
+ timeout: 0
+ },
+ {
+ default: () => (show.value ? h(Async) : null),
+ fallback: h('div', 'fallback')
+ }
+ )
+ }
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ expect(onFallback).toHaveBeenCalledTimes(1)
+ expect(onPending).toHaveBeenCalledTimes(1)
+ expect(onResolve).toHaveBeenCalledTimes(0)
+
+ await Promise.all(deps)
+ await nextTick()
+ expect(onFallback).toHaveBeenCalledTimes(1)
+ expect(onPending).toHaveBeenCalledTimes(1)
+ expect(onResolve).toHaveBeenCalledTimes(1)
+
+ show.value = false
+ await nextTick()
+ expect(onFallback).toHaveBeenCalledTimes(1)
+ expect(onPending).toHaveBeenCalledTimes(2)
+ expect(onResolve).toHaveBeenCalledTimes(2)
+
+ deps.length = 0
+ show.value = true
+ await nextTick()
+ expect(onFallback).toHaveBeenCalledTimes(2)
+ expect(onPending).toHaveBeenCalledTimes(3)
+ expect(onResolve).toHaveBeenCalledTimes(2)
+
+ await Promise.all(deps)
+ await nextTick()
+ expect(onFallback).toHaveBeenCalledTimes(2)
+ expect(onPending).toHaveBeenCalledTimes(3)
+ expect(onResolve).toHaveBeenCalledTimes(3)
+ })
+
test('nested async deps', async () => {
const calls: string[] = []
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index d5d544469f3..5ef07a2ac5f 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -94,6 +94,16 @@ export const Suspense = ((__FEATURE_SUSPENSE__
new (): { $props: VNodeProps & SuspenseProps }
}
+function triggerEvent(
+ vnode: VNode,
+ name: 'onResolve' | 'onPending' | 'onFallback'
+) {
+ const eventListener = vnode.props && vnode.props[name]
+ if (isFunction(eventListener)) {
+ eventListener()
+ }
+}
+
function mountSuspense(
vnode: VNode,
container: RendererElement,
@@ -137,6 +147,10 @@ function mountSuspense(
// now check if we have encountered any async deps
if (suspense.deps > 0) {
// has async
+ // invoke @fallback event
+ triggerEvent(vnode, 'onPending')
+ triggerEvent(vnode, 'onFallback')
+
// mount the fallback tree
patch(
null,
@@ -304,10 +318,7 @@ function patchSuspense(
} else {
// root node toggled
// invoke @pending event
- const onPending = n2.props && n2.props.onPending
- if (isFunction(onPending)) {
- onPending()
- }
+ triggerEvent(n2, 'onPending')
// mount pending branch in off-dom container
suspense.pendingBranch = newBranch
suspense.pendingId++
@@ -501,10 +512,7 @@ function createSuspenseBoundary(
suspense.effects = []
// invoke @resolve event
- const onResolve = vnode.props && vnode.props.onResolve
- if (isFunction(onResolve)) {
- onResolve()
- }
+ triggerEvent(vnode, 'onResolve')
},
fallback(fallbackVNode) {
@@ -521,10 +529,7 @@ function createSuspenseBoundary(
} = suspense
// invoke @fallback event
- const onFallback = vnode.props && vnode.props.onFallback
- if (isFunction(onFallback)) {
- onFallback()
- }
+ triggerEvent(vnode, 'onFallback')
const anchor = next(activeBranch!)
const mountFallback = () => {