@@ -172,7 +172,7 @@ const invalidated = [];
172
172
*/
173
173
const components = [ ] ;
174
174
175
- /** @type {{id: string, promise: Promise<import('./types.js').NavigationResult>} | null } */
175
+ /** @type {{id: string, token: {}, promise: Promise<import('./types.js').NavigationResult>} | null } */
176
176
let load_cache = null ;
177
177
178
178
/** @type {Array<(navigation: import('@sveltejs/kit').BeforeNavigate) => void> } */
@@ -219,6 +219,14 @@ let page;
219
219
/** @type {{} } */
220
220
let token ;
221
221
222
+ /**
223
+ * A set of tokens which are associated to current preloads.
224
+ * If a preload becomes a real navigation, it's removed from the set.
225
+ * If a preload token is in the set and the preload errors, the error
226
+ * handling logic (for example reloading) is skipped.
227
+ */
228
+ const preload_tokens = new Set ( ) ;
229
+
222
230
/** @type {Promise<void> | null } */
223
231
let pending_invalidate ;
224
232
@@ -375,16 +383,26 @@ async function _goto(url, options, redirect_count, nav_token) {
375
383
376
384
/** @param {import('./types.js').NavigationIntent } intent */
377
385
async function _preload_data ( intent ) {
378
- load_cache = {
379
- id : intent . id ,
380
- promise : load_route ( intent ) . then ( ( result ) => {
381
- if ( result . type === 'loaded' && result . state . error ) {
382
- // Don't cache errors, because they might be transient
383
- load_cache = null ;
384
- }
385
- return result ;
386
- } )
387
- } ;
386
+ // Reuse the existing pending preload if it's for the same navigation.
387
+ // Prevents an edge case where same preload is triggered multiple times,
388
+ // then a later one is becoming the real navigation and the preload tokens
389
+ // get out of sync.
390
+ if ( intent . id !== load_cache ?. id ) {
391
+ const preload = { } ;
392
+ preload_tokens . add ( preload ) ;
393
+ load_cache = {
394
+ id : intent . id ,
395
+ token : preload ,
396
+ promise : load_route ( { ...intent , preload } ) . then ( ( result ) => {
397
+ preload_tokens . delete ( preload ) ;
398
+ if ( result . type === 'loaded' && result . state . error ) {
399
+ // Don't cache errors, because they might be transient
400
+ load_cache = null ;
401
+ }
402
+ return result ;
403
+ } )
404
+ } ;
405
+ }
388
406
389
407
return load_cache . promise ;
390
408
}
@@ -803,11 +821,31 @@ function diff_search_params(old_url, new_url) {
803
821
}
804
822
805
823
/**
806
- * @param {import('./types.js').NavigationIntent } intent
824
+ * @param {Omit<import('./types.js').NavigationFinished['state'], 'branch'> & { error: App.Error } } opts
825
+ * @returns {import('./types.js').NavigationFinished }
826
+ */
827
+ function preload_error ( { error, url, route, params } ) {
828
+ return {
829
+ type : 'loaded' ,
830
+ state : {
831
+ error,
832
+ url,
833
+ route,
834
+ params,
835
+ branch : [ ]
836
+ } ,
837
+ props : { page, constructors : [ ] }
838
+ } ;
839
+ }
840
+
841
+ /**
842
+ * @param {import('./types.js').NavigationIntent & { preload?: {} } } intent
807
843
* @returns {Promise<import('./types.js').NavigationResult> }
808
844
*/
809
- async function load_route ( { id, invalidating, url, params, route } ) {
845
+ async function load_route ( { id, invalidating, url, params, route, preload } ) {
810
846
if ( load_cache ?. id === id ) {
847
+ // the preload becomes the real navigation
848
+ preload_tokens . delete ( load_cache . token ) ;
811
849
return load_cache . promise ;
812
850
}
813
851
@@ -855,9 +893,15 @@ async function load_route({ id, invalidating, url, params, route }) {
855
893
try {
856
894
server_data = await load_data ( url , invalid_server_nodes ) ;
857
895
} catch ( error ) {
896
+ const handled_error = await handle_error ( error , { url, params, route : { id } } ) ;
897
+
898
+ if ( preload_tokens . has ( preload ) ) {
899
+ return preload_error ( { error : handled_error , url, params, route } ) ;
900
+ }
901
+
858
902
return load_root_error_page ( {
859
903
status : get_status ( error ) ,
860
- error : await handle_error ( error , { url , params , route : { id : route . id } } ) ,
904
+ error : handled_error ,
861
905
url,
862
906
route
863
907
} ) ;
@@ -940,6 +984,15 @@ async function load_route({ id, invalidating, url, params, route }) {
940
984
} ;
941
985
}
942
986
987
+ if ( preload_tokens . has ( preload ) ) {
988
+ return preload_error ( {
989
+ error : await handle_error ( err , { params, url, route : { id : route . id } } ) ,
990
+ url,
991
+ params,
992
+ route
993
+ } ) ;
994
+ }
995
+
943
996
let status = get_status ( err ) ;
944
997
/** @type {App.Error } */
945
998
let error ;
@@ -972,8 +1025,6 @@ async function load_route({ id, invalidating, url, params, route }) {
972
1025
route
973
1026
} ) ;
974
1027
} else {
975
- // if we get here, it's because the root `load` function failed,
976
- // and we need to fall back to the server
977
1028
return await server_fallback ( url , { id : route . id } , error , status ) ;
978
1029
}
979
1030
}
0 commit comments