refactor: extract #fetchChunk, replace Promise.allSettled with self-describing results

This commit is contained in:
Ilia Mashkov
2026-04-04 09:58:41 +03:00
parent 20110168f2
commit 5a065ae5a1

View File

@@ -191,34 +191,7 @@ export class AppliedFontsManager {
// ==================== PHASE 1: Concurrent Fetching ==================== // ==================== PHASE 1: Concurrent Fetching ====================
// Fetch multiple font files in parallel since network I/O is non-blocking // Fetch multiple font files in parallel since network I/O is non-blocking
for (let i = 0; i < entries.length; i += concurrency) { for (let i = 0; i < entries.length; i += concurrency) {
// Process in chunks based on concurrency limit await this.#fetchChunk(entries.slice(i, i + concurrency), buffers);
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);
}
}
} }
// ==================== PHASE 2: Sequential Parsing ==================== // ==================== 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<string, ArrayBuffer>,
): Promise<void> {
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`, * 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. * and updates reactive status. On failure, sets status to `'error'` and increments the retry count.