How does a child communicate to a parent that it needs to suspend? Typically, we would pass a setState prop to the child (e.g. setLoading) that the child could use to dispatch events to a parent. But this "prop drilling":
- Clutters code (especially when passing many levels deep)
- Requires increasingly complex conditions to synchronize loading states (e.g. the parent should show a loading component until childA and childB loading state is false)
React's solution is interesting... Instead of passing many setState props, where children dispatch events to parents, React Suspense uses javascript's existing "throw" statement as an event emitter.
So a component tells React to Suspend by "throwing a promise". This exception will then "bubble up", as exceptions do, to the nearest Suspense Boundary. So the suspense boundary is similar to a catch block when throwing an error. When a suspense boundary catches a thrown promise, it will show a fallback component until the promise is resolved. When the promise is resolved, React will attempt to re-render the component that threw the promise.
Throwing a promise is a creative use of exception bubbling. The benefit is that it greatly reduces "prop drilling", since a child component many levels deep can simply "throw a promise" and it will bubble up. And orchestrating loading sequences becomes easier, since multiple siblings can throw promises, and a single parent Suspense "Fallback" boundary component will show until all children's promises are resolved (without defining many conditions in the parent). Though, while throwing promises is convenient, it does slightly bend the pragmatics of exceptions, explained eloquently by this Svelte maintainer.
Another interesting note about Suspense is that throwing a promise does not block execution entirely! When a React Suspense boundary catches a thrown promise and commits a fallback component to the DOM, it will continue attempting to render other parts of your component tree. E.g. In a component with 4 children, where child 1 throws a promise – React will catch the thrown promise, commit a fallback element, and continue attempting to render the other 3 child components while it waits for child 1's promise to resolve! (It might even continue attempting to render parts of child 1's tree. This SuspenseImage article has interesting notes on this topic in the "Waterfall" section).