fix(image-pipeline): share source bitmap cache for abortable loads
This commit is contained in:
@@ -10,34 +10,10 @@ function throwIfAborted(signal: AbortSignal | undefined): void {
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSourceBitmap(
|
||||
sourceUrl: string,
|
||||
options: LoadSourceBitmapOptions = {},
|
||||
): Promise<ImageBitmap> {
|
||||
if (!sourceUrl || sourceUrl.trim().length === 0) {
|
||||
throw new Error("Render sourceUrl is required.");
|
||||
}
|
||||
|
||||
if (typeof createImageBitmap !== "function") {
|
||||
throw new Error("ImageBitmap is not available in this environment.");
|
||||
}
|
||||
|
||||
throwIfAborted(options.signal);
|
||||
|
||||
if (options.signal) {
|
||||
const response = await fetch(sourceUrl, { signal: options.signal });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Render source failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
throwIfAborted(options.signal);
|
||||
return await createImageBitmap(blob);
|
||||
}
|
||||
|
||||
function getOrCreateSourceBitmapPromise(sourceUrl: string): Promise<ImageBitmap> {
|
||||
const cached = imageBitmapCache.get(sourceUrl);
|
||||
if (cached) {
|
||||
return await cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
@@ -52,10 +28,68 @@ export async function loadSourceBitmap(
|
||||
|
||||
imageBitmapCache.set(sourceUrl, promise);
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} catch (error) {
|
||||
imageBitmapCache.delete(sourceUrl);
|
||||
throw error;
|
||||
}
|
||||
void promise.catch(() => {
|
||||
if (imageBitmapCache.get(sourceUrl) === promise) {
|
||||
imageBitmapCache.delete(sourceUrl);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function awaitWithLocalAbort<T>(
|
||||
promise: Promise<T>,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<T> {
|
||||
throwIfAborted(signal);
|
||||
|
||||
if (!signal) {
|
||||
return await promise;
|
||||
}
|
||||
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
const abortError = () => new DOMException("The operation was aborted.", "AbortError");
|
||||
|
||||
const cleanup = () => {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
};
|
||||
|
||||
const onAbort = () => {
|
||||
cleanup();
|
||||
reject(abortError());
|
||||
};
|
||||
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
|
||||
promise.then(
|
||||
(value) => {
|
||||
cleanup();
|
||||
if (signal.aborted) {
|
||||
reject(abortError());
|
||||
return;
|
||||
}
|
||||
resolve(value);
|
||||
},
|
||||
(error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadSourceBitmap(
|
||||
sourceUrl: string,
|
||||
options: LoadSourceBitmapOptions = {},
|
||||
): Promise<ImageBitmap> {
|
||||
if (!sourceUrl || sourceUrl.trim().length === 0) {
|
||||
throw new Error("Render sourceUrl is required.");
|
||||
}
|
||||
|
||||
if (typeof createImageBitmap !== "function") {
|
||||
throw new Error("ImageBitmap is not available in this environment.");
|
||||
}
|
||||
|
||||
const promise = getOrCreateSourceBitmapPromise(sourceUrl);
|
||||
return await awaitWithLocalAbort(promise, options.signal);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user