diff --git a/ts/components/loader.ts b/ts/components/loader.ts index 77ea7eb4c..eb00e70b0 100644 --- a/ts/components/loader.ts +++ b/ts/components/loader.ts @@ -169,6 +169,13 @@ export const Loader = { */ versions: new Map(), + /** + * Array of nested load promises so if component performs additional + * loads (like an output jax with an alternate font), then the + * outer load promises wont resolve until the inner ones are complete. + */ + nestedLoads: [] as Promise[], + /** * Get a promise that is resolved when all the named packages have been loaded. * @@ -191,37 +198,83 @@ export const Loader = { * Load the named packages and return a promise that is resolved when they are all loaded * * @param {string[]} names The packages to load - * @returns {Promise} A promise that resolves when all the named packages are ready + * @returns {Promise} A promise that resolves when all the named packages are ready */ load(...names: string[]): Promise { if (names.length === 0) { return Promise.resolve([]); } - const promises = []; - for (const name of names) { - let extension = Package.packages.get(name); - if (!extension) { - extension = new Package(name); - extension.provides(CONFIG.provides[name]); + // + // Add a new array to store promises for this load() call + // + let nested = [] as Promise[]; + this.nestedLoads.unshift(nested); + // + // Create a promise for this load() call + // + const promise = Promise.resolve().then(async () => { + // + // Collect the promises for all the named packages, + // creating the package if needed, and add checks + // for the verions numbers used in the components. + // + const promises = []; + for (const name of names) { + let extension = Package.packages.get(name); + if (!extension) { + extension = new Package(name); + extension.provides(CONFIG.provides[name]); + } + extension.checkNoLoad(); + promises.push( + extension.promise.then(() => { + if ( + CONFIG.versionWarnings && + extension.isLoaded && + !Loader.versions.has(Package.resolvePath(name)) + ) { + console.warn( + `No version information available for component ${name}` + ); + } + return extension.result; + }) as Promise + ); } - extension.checkNoLoad(); - promises.push( - extension.promise.then(() => { - if ( - CONFIG.versionWarnings && - extension.isLoaded && - !Loader.versions.has(Package.resolvePath(name)) - ) { - console.warn( - `No version information available for component ${name}` - ); - } - return extension.result; - }) as Promise - ); - } - Package.loadAll(); - return Promise.all(promises); + // + // Load everything that was requested and wait for + // them to be loaded. + // + Package.loadAll(); + const result = await Promise.all(promises); + // + // If any other loads occurred while we were waiting, + // Wait for those promises, and clear the list so that + // if even MORE loads occur while waiting for those, + // we can wait for them, too. Keep doing that until + // no additional loads occurred, in which case we are + // now done. + // + while (nested.length) { + const promise = Promise.all(nested); + nested = this.nestedLoads[this.nestedLoads.indexOf(nested)] = []; + await promise; + } + // + // Remove the (empty) list from the nested list, + // and return the result. + // + this.nestedLoads.splice(this.nestedLoads.indexOf(nested), 1); + return result; + }); + // + // Add this load promise to the lists for any parent load() call that are + // pending when this load() was performed, then return the load promise. + // + this.nestedLoads + .slice(1) + .forEach((list: Promise[]) => list.push(promise)); + return promise; }, /** diff --git a/ts/components/package.ts b/ts/components/package.ts index 04f9579cc..f18832cd2 100644 --- a/ts/components/package.ts +++ b/ts/components/package.ts @@ -159,9 +159,9 @@ export class Package { */ public static loadPromise(name: string): Promise { const config = (CONFIG[name] || {}) as PackageConfig; - const promise = Promise.all( - (config.extraLoads || []).map((name) => Loader.load(name)) - ); + const promise = config.extraLoads + ? Loader.load(...config.extraLoads) + : Promise.resolve(); const checkReady = config.checkReady || (() => Promise.resolve()); return promise.then(() => checkReady()) as Promise; }