@@ -4,6 +4,7 @@ import { createControlledPromise, isPromise } from './utils'
44import { isNotFound } from './not-found'
55import { rootRouteId } from './root'
66import { isRedirect } from './redirect'
7+ import type { Awaitable } from './utils'
78import type { NotFoundError } from './not-found'
89import type { ParsedLocation } from './location'
910import type {
@@ -34,7 +35,7 @@ type InnerLoadContext = {
3435 onReady ?: ( ) => Promise < void >
3536 sync ?: boolean
3637 /** mutable state, scoped to a `loadMatches` call */
37- matchPromises : Array < Promise < AnyRouteMatch > >
38+ matchPromises : Array < Awaitable < AnyRouteMatch > >
3839}
3940
4041const triggerOnReady = ( inner : InnerLoadContext ) : void | Promise < void > => {
@@ -703,10 +704,10 @@ const runLoader = async (
703704 }
704705}
705706
706- const loadRouteMatch = async (
707+ const loadRouteMatch = (
707708 inner : InnerLoadContext ,
708709 index : number ,
709- ) : Promise < AnyRouteMatch > => {
710+ ) : Awaitable < AnyRouteMatch > => {
710711 const { id : matchId , routeId } = inner . matches [ index ] !
711712 let loaderShouldRunAsync = false
712713 let loaderIsRunningAsync = false
@@ -716,121 +717,140 @@ const loadRouteMatch = async (
716717 if ( inner . router . isServer ) {
717718 const headResult = executeHead ( inner , matchId , route )
718719 if ( headResult ) {
719- const head = await headResult
720- inner . updateMatch ( matchId , ( prev ) => ( {
721- ...prev ,
722- ...head ,
723- } ) )
720+ return headResult . then ( ( head ) => {
721+ inner . updateMatch ( matchId , ( prev ) => ( {
722+ ...prev ,
723+ ...head ,
724+ } ) )
725+ return inner . router . getMatch ( matchId ) !
726+ } )
724727 }
725728 return inner . router . getMatch ( matchId ) !
726729 }
727- } else {
728- const prevMatch = inner . router . getMatch ( matchId ) !
729- // there is a loaderPromise, so we are in the middle of a load
730- if ( prevMatch . _nonReactive . loaderPromise ) {
731- // do not block if we already have stale data we can show
732- // but only if the ongoing load is not a preload since error handling is different for preloads
733- // and we don't want to swallow errors
734- if ( prevMatch . status === 'success' && ! inner . sync && ! prevMatch . preload ) {
735- return prevMatch
736- }
737- await prevMatch . _nonReactive . loaderPromise
730+ return settleLoadRouteMatch ( )
731+ }
732+
733+ const prevMatch = inner . router . getMatch ( matchId ) !
734+
735+ // there is a loaderPromise, so we are in the middle of a load
736+ if ( prevMatch . _nonReactive . loaderPromise ) {
737+ // do not block if we already have stale data we can show
738+ // but only if the ongoing load is not a preload since error handling is different for preloads
739+ // and we don't want to swallow errors
740+ if ( prevMatch . status === 'success' && ! inner . sync && ! prevMatch . preload ) {
741+ return prevMatch
742+ }
743+ return prevMatch . _nonReactive . loaderPromise . then ( ( ) => {
738744 const match = inner . router . getMatch ( matchId ) !
739745 if ( match . error ) {
740746 handleRedirectAndNotFound ( inner , match , match . error )
741747 }
742- } else {
743- // This is where all of the stale-while-revalidate magic happens
744- const age = Date . now ( ) - prevMatch . updatedAt
745-
746- const preload = resolvePreload ( inner , matchId )
747-
748- const staleAge = preload
749- ? ( route . options . preloadStaleTime ??
750- inner . router . options . defaultPreloadStaleTime ??
751- 30_000 ) // 30 seconds for preloads by default
752- : ( route . options . staleTime ??
753- inner . router . options . defaultStaleTime ??
754- 0 )
755-
756- const shouldReloadOption = route . options . shouldReload
757-
758- // Default to reloading the route all the time
759- // Allow shouldReload to get the last say,
760- // if provided.
761- const shouldReload =
762- typeof shouldReloadOption === 'function'
763- ? shouldReloadOption ( getLoaderContext ( inner , matchId , index , route ) )
764- : shouldReloadOption
765-
766- const nextPreload =
767- ! ! preload && ! inner . router . state . matches . some ( ( d ) => d . id === matchId )
768- const match = inner . router . getMatch ( matchId ) !
769- match . _nonReactive . loaderPromise = createControlledPromise < void > ( )
770- if ( nextPreload !== match . preload ) {
771- inner . updateMatch ( matchId , ( prev ) => ( {
772- ...prev ,
773- preload : nextPreload ,
774- } ) )
775- }
748+ return settleLoadRouteMatch ( )
749+ } )
750+ }
751+
752+ // This is where all of the stale-while-revalidate magic happens
753+ const age = Date . now ( ) - prevMatch . updatedAt
754+
755+ const preload = resolvePreload ( inner , matchId )
756+
757+ const staleAge = preload
758+ ? ( route . options . preloadStaleTime ??
759+ inner . router . options . defaultPreloadStaleTime ??
760+ 30_000 ) // 30 seconds for preloads by default
761+ : ( route . options . staleTime ?? inner . router . options . defaultStaleTime ?? 0 )
762+
763+ const shouldReloadOption = route . options . shouldReload
764+
765+ // Default to reloading the route all the time
766+ // Allow shouldReload to get the last say,
767+ // if provided.
768+ const shouldReload =
769+ typeof shouldReloadOption === 'function'
770+ ? shouldReloadOption ( getLoaderContext ( inner , matchId , index , route ) )
771+ : shouldReloadOption
772+
773+ const nextPreload =
774+ ! ! preload && ! inner . router . state . matches . some ( ( d ) => d . id === matchId )
775+ const match = inner . router . getMatch ( matchId ) !
776+ match . _nonReactive . loaderPromise = createControlledPromise < void > ( )
777+ if ( nextPreload !== match . preload ) {
778+ inner . updateMatch ( matchId , ( prev ) => ( {
779+ ...prev ,
780+ preload : nextPreload ,
781+ } ) )
782+ }
776783
777- // If the route is successful and still fresh, just resolve
778- const { status, invalid } = match
779- loaderShouldRunAsync =
780- status === 'success' && ( invalid || ( shouldReload ?? age > staleAge ) )
781- if ( preload && route . options . preload === false ) {
782- // Do nothing
783- } else if ( loaderShouldRunAsync && ! inner . sync ) {
784- loaderIsRunningAsync = true
785- ; ( async ( ) => {
786- try {
787- await runLoader ( inner , matchId , index , route )
788- const match = inner . router . getMatch ( matchId ) !
789- match . _nonReactive . loaderPromise ?. resolve ( )
790- match . _nonReactive . loadPromise ?. resolve ( )
791- match . _nonReactive . loaderPromise = undefined
792- } catch ( err ) {
793- if ( isRedirect ( err ) ) {
794- await inner . router . navigate ( err . options )
795- }
796- }
797- } ) ( )
798- } else if ( status !== 'success' || ( loaderShouldRunAsync && inner . sync ) ) {
784+ if ( preload && route . options . preload === false ) {
785+ // Do nothing
786+ return settleLoadRouteMatch ( )
787+ }
788+
789+ // If the route is successful and still fresh, just resolve
790+ const { status, invalid } = match
791+ loaderShouldRunAsync =
792+ status === 'success' && ( invalid || ( shouldReload ?? age > staleAge ) )
793+ if ( loaderShouldRunAsync && ! inner . sync ) {
794+ loaderIsRunningAsync = true
795+ ; ( async ( ) => {
796+ try {
799797 await runLoader ( inner , matchId , index , route )
800- } else {
801- // if the loader did not run, still update head.
802- // reason: parent's beforeLoad may have changed the route context
803- // and only now do we know the route context (and that the loader would not run)
804- const headResult = executeHead ( inner , matchId , route )
805- if ( headResult ) {
806- const head = await headResult
807- inner . updateMatch ( matchId , ( prev ) => ( {
808- ...prev ,
809- ...head ,
810- } ) )
798+ const match = inner . router . getMatch ( matchId ) !
799+ match . _nonReactive . loaderPromise ?. resolve ( )
800+ match . _nonReactive . loadPromise ?. resolve ( )
801+ match . _nonReactive . loaderPromise = undefined
802+ } catch ( err ) {
803+ if ( isRedirect ( err ) ) {
804+ await inner . router . navigate ( err . options )
811805 }
812806 }
813- }
807+ } ) ( )
808+ return settleLoadRouteMatch ( )
814809 }
815- const match = inner . router . getMatch ( matchId ) !
816- if ( ! loaderIsRunningAsync ) {
817- match . _nonReactive . loaderPromise ?. resolve ( )
818- match . _nonReactive . loadPromise ?. resolve ( )
810+
811+ if ( status !== 'success' || ( loaderShouldRunAsync && inner . sync ) ) {
812+ return runLoader ( inner , matchId , index , route ) . then ( settleLoadRouteMatch )
819813 }
820814
821- clearTimeout ( match . _nonReactive . pendingTimeout )
822- match . _nonReactive . pendingTimeout = undefined
823- if ( ! loaderIsRunningAsync ) match . _nonReactive . loaderPromise = undefined
824- match . _nonReactive . dehydrated = undefined
825- const nextIsFetching = loaderIsRunningAsync ? match . isFetching : false
826- if ( nextIsFetching !== match . isFetching || match . invalid !== false ) {
827- inner . updateMatch ( matchId , ( prev ) => ( {
828- ...prev ,
829- isFetching : nextIsFetching ,
830- invalid : false ,
831- } ) )
832- return inner . router . getMatch ( matchId ) !
833- } else {
815+ // if the loader did not run, still update head.
816+ // reason: parent's beforeLoad may have changed the route context
817+ // and only now do we know the route context (and that the loader would not run)
818+ const headResult = executeHead ( inner , matchId , route )
819+ if ( headResult ) {
820+ return headResult . then ( ( head ) => {
821+ inner . updateMatch ( matchId , ( prev ) => ( {
822+ ...prev ,
823+ ...head ,
824+ } ) )
825+ return settleLoadRouteMatch ( )
826+ } )
827+ }
828+
829+ return settleLoadRouteMatch ( )
830+
831+ function settleLoadRouteMatch ( ) {
832+ const match = inner . router . getMatch ( matchId ) !
833+
834+ if ( ! loaderIsRunningAsync ) {
835+ match . _nonReactive . loaderPromise ?. resolve ( )
836+ match . _nonReactive . loadPromise ?. resolve ( )
837+ match . _nonReactive . loaderPromise = undefined
838+ }
839+
840+ clearTimeout ( match . _nonReactive . pendingTimeout )
841+ match . _nonReactive . pendingTimeout = undefined
842+ match . _nonReactive . dehydrated = undefined
843+
844+ const nextIsFetching = loaderIsRunningAsync ? match . isFetching : false
845+ if ( nextIsFetching !== match . isFetching || match . invalid !== false ) {
846+ inner . updateMatch ( matchId , ( prev ) => ( {
847+ ...prev ,
848+ isFetching : nextIsFetching ,
849+ invalid : false ,
850+ } ) )
851+ return inner . router . getMatch ( matchId ) !
852+ }
853+
834854 return match
835855 }
836856}
@@ -866,10 +886,13 @@ export async function loadMatches(arg: {
866886
867887 // Execute all loaders in parallel
868888 const max = inner . firstBadMatchIndex ?? inner . matches . length
889+ let hasPromises = false
869890 for ( let i = 0 ; i < max ; i ++ ) {
870- inner . matchPromises . push ( loadRouteMatch ( inner , i ) )
891+ const result = loadRouteMatch ( inner , i )
892+ inner . matchPromises . push ( result )
893+ if ( ! hasPromises && isPromise ( result ) ) hasPromises = true
871894 }
872- await Promise . all ( inner . matchPromises )
895+ if ( hasPromises ) await Promise . all ( inner . matchPromises )
873896
874897 const readyPromise = triggerOnReady ( inner )
875898 if ( isPromise ( readyPromise ) ) await readyPromise
0 commit comments