From 5a065ae5a128fe95b637c9987ffc47c2ba0cbf9a Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 4 Apr 2026 09:58:41 +0300 Subject: [PATCH] refactor: extract #fetchChunk, replace Promise.allSettled with self-describing results --- .../appliedFontsStore.svelte.ts | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts index 33becd1..789f4a1 100644 --- a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts +++ b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts @@ -191,34 +191,7 @@ export class AppliedFontsManager { // ==================== PHASE 1: Concurrent Fetching ==================== // Fetch multiple font files in parallel since network I/O is non-blocking for (let i = 0; i < entries.length; i += concurrency) { - // Process in chunks based on concurrency limit - const chunk = entries.slice(i, i + concurrency); - const results = await Promise.allSettled( - chunk.map(async ([key, config]) => { - this.statuses.set(key, 'loading'); - // Fetch buffer via cache (checks memory → Cache API → network) - const buffer = await this.#cache.get(config.url, this.#abortController.signal); - buffers.set(key, buffer); - }), - ); - - // Handle fetch errors - set status and increment retry count - for (let j = 0; j < results.length; j++) { - if (results[j].status === 'rejected') { - const [key, config] = chunk[j]; - const reason = (results[j] as PromiseRejectedResult).reason; - // Aborted fetches are not retriable failures — skip silently - const isAbort = reason instanceof FontFetchError - && reason.cause instanceof Error - && reason.cause.name === 'AbortError'; - if (isAbort) continue; - if (reason instanceof FontFetchError) { - console.error(`Font fetch failed: ${config.name}`, reason); - } - this.statuses.set(key, 'error'); - this.#queue.incrementRetry(key); - } - } + await this.#fetchChunk(entries.slice(i, i + concurrency), buffers); } // ==================== PHASE 2: Sequential Parsing ==================== @@ -250,6 +223,43 @@ export class AppliedFontsManager { } } + /** + * Fetches a chunk of fonts concurrently and populates `buffers` with successful results. + * Each promise carries its own key and config so results need no index correlation. + * Aborted fetches are silently skipped; other errors set status to `'error'` and increment retry. + */ + async #fetchChunk( + chunk: Array<[string, FontLoadRequestConfig]>, + buffers: Map, + ): Promise { + const results = await Promise.all( + chunk.map(async ([key, config]) => { + this.statuses.set(key, 'loading'); + try { + const buffer = await this.#cache.get(config.url, this.#abortController.signal); + buffers.set(key, buffer); + return { ok: true as const, key }; + } catch (reason) { + return { ok: false as const, key, config, reason }; + } + }), + ); + + for (const result of results) { + if (result.ok) continue; + const { key, config, reason } = result; + const isAbort = reason instanceof FontFetchError + && reason.cause instanceof Error + && reason.cause.name === 'AbortError'; + if (isAbort) continue; + if (reason instanceof FontFetchError) { + console.error(`Font fetch failed: ${config.name}`, reason); + } + this.statuses.set(key, 'error'); + this.#queue.incrementRetry(key); + } + } + /** * Parses a fetched buffer into a {@link FontFace}, registers it with `document.fonts`, * and updates reactive status. On failure, sets status to `'error'` and increments the retry count.