44 type Readable ,
55 writable ,
66 StartStopNotifier ,
7+ readable ,
78} from 'svelte/store' ;
89import type {
910 AsyncStoreOptions ,
@@ -20,6 +21,7 @@ import {
2021 getStoresArray ,
2122 reloadAll ,
2223 loadAll ,
24+ rebounce ,
2325} from '../utils/index.js' ;
2426import { flagStoreCreated , getStoreTestingMode , logError } from '../config.js' ;
2527
@@ -66,44 +68,128 @@ export const asyncWritable = <S extends Stores, T>(
6668 flagStoreCreated ( ) ;
6769 const { reloadable, trackState, initial } = options ;
6870
69- const loadState = trackState
70- ? writable < LoadState > ( getLoadState ( 'LOADING' ) )
71- : undefined ;
72- const setState = ( state : State ) => loadState ? .set ( getLoadState ( state ) ) ;
71+ const rebouncedMappingLoad = rebounce ( mappingLoadFunction ) ;
72+
73+ const loadState = writable < LoadState > ( getLoadState ( 'LOADING' ) ) ;
74+ const setState = ( state : State ) => loadState . set ( getLoadState ( state ) ) ;
7375
7476 // flag marking whether store is ready for updates from subscriptions
7577 let ready = false ;
78+ let changeReceived = false ;
7679
7780 // most recent call of mappingLoadFunction, including resulting side effects
7881 // (updating store value, tracking state, etc)
7982 let currentLoadPromise : Promise < T > ;
83+ let resolveCurrentLoad : ( value : T | PromiseLike < T > ) => void ;
84+ let rejectCurrentLoad : ( reason : Error ) => void ;
85+
86+ const setCurrentLoadPromise = ( ) => {
87+ currentLoadPromise = new Promise ( ( resolve , reject ) => {
88+ resolveCurrentLoad = resolve ;
89+ rejectCurrentLoad = reject ;
90+ } ) ;
91+ } ;
8092
81- let latestLoadAndSet : ( ) => Promise < T > ;
93+ let parentValues : StoresValues < S > ;
94+
95+ const mappingLoadThenSet = async ( setStoreValue ) => {
96+ if ( get ( loadState ) . isSettled ) {
97+ setCurrentLoadPromise ( ) ;
98+ setState ( 'RELOADING' ) ;
99+ }
82100
83- const tryLoadValue = async ( values : StoresValues < S > ) => {
84101 try {
85- return await mappingLoadFunction ( values ) ;
102+ const finalValue = await rebouncedMappingLoad ( parentValues ) ;
103+ setStoreValue ( finalValue ) ;
104+ resolveCurrentLoad ( finalValue ) ;
105+ setState ( 'LOADED' ) ;
86106 } catch ( e ) {
87107 if ( e . name !== 'AbortError' ) {
88- logError ( e ) ;
89108 setState ( 'ERROR' ) ;
109+ rejectCurrentLoad ( e ) ;
90110 }
91- throw e ;
92111 }
93112 } ;
94113
95- const loadThenSet = async ( set ) => { } ;
114+ const onFirstSubscription : StartStopNotifier < T > = ( setStoreValue ) => {
115+ setCurrentLoadPromise ( ) ;
96116
97- const onFirstSubscribtion : StartStopNotifier < T > = async ( set ) => {
98- ( async ( ) => {
99- const values = await loadAll ( stores ) ;
100- ready = true ;
101- tryLoadValue ( values ) ;
102- } ) ( ) ;
103- const values = await loadAll ( ) ;
117+ const initialLoad = async ( ) => {
118+ try {
119+ parentValues = await loadAll ( stores ) ;
120+ ready = true ;
121+ changeReceived = false ;
122+ mappingLoadThenSet ( setStoreValue ) ;
123+ } catch ( error ) {
124+ rejectCurrentLoad ( error ) ;
125+ }
126+ } ;
127+ initialLoad ( ) ;
128+
129+ const parentUnsubscribers = getStoresArray ( stores ) . map ( ( store , i ) =>
130+ store . subscribe ( ( value ) => {
131+ changeReceived = true ;
132+ if ( Array . isArray ( stores ) ) {
133+ parentValues [ i ] = value ;
134+ } else {
135+ parentValues = value as any ;
136+ }
137+ if ( ready ) {
138+ mappingLoadThenSet ( setStoreValue ) ;
139+ }
140+ } )
141+ ) ;
142+
143+ // called on losing last subscriber
144+ return ( ) => {
145+ parentUnsubscribers . map ( ( unsubscriber ) => unsubscriber ( ) ) ;
146+ } ;
147+ } ;
148+
149+ const thisStore = writable ( initial , onFirstSubscription ) ;
150+
151+ const subscribe = thisStore . subscribe ;
152+ const load = async ( ) => {
153+ const dummyUnsubscribe = thisStore . subscribe ( ( ) => {
154+ /* no-op */
155+ } ) ;
156+ try {
157+ const result = await currentLoadPromise ;
158+ dummyUnsubscribe ( ) ;
159+ return result ;
160+ } catch ( error ) {
161+ dummyUnsubscribe ( ) ;
162+ throw error ;
163+ }
164+ } ;
165+ const reload = async ( visitedMap ?: VisitedMap ) => {
166+ ready = false ;
167+ changeReceived = false ;
168+ setCurrentLoadPromise ( ) ;
169+ setState ( 'RELOADING' ) ;
170+
171+ const visitMap = visitedMap ?? new WeakMap ( ) ;
172+ await reloadAll ( stores , visitMap ) ;
173+ ready = true ;
174+ if ( changeReceived || reloadable ) {
175+ mappingLoadThenSet ( thisStore . set ) ;
176+ } else {
177+ resolveCurrentLoad ( get ( thisStore ) ) ;
178+ }
179+ return currentLoadPromise ;
104180 } ;
105- } ;
106181
182+ return {
183+ get store ( ) {
184+ return this ;
185+ } ,
186+ subscribe,
187+ load,
188+ reload,
189+ set : ( ) => Promise . resolve ( ) ,
190+ update : ( ) => Promise . resolve ( ) ,
191+ } ;
192+ } ;
107193/**
108194 * Generate a Loadable store that is considered 'loaded' after resolving asynchronous behavior.
109195 * This asynchronous behavior may be derived from the value of parent Loadable or non Loadable stores.
0 commit comments