export interface ReadFileAsDataUrlOptions { onProgress?: (progress: number) => void signal?: AbortSignal } export function readFileAsDataUrl( file: File, { onProgress, signal }: ReadFileAsDataUrlOptions = {} ): Promise { return new Promise((resolve, reject) => { const reader = new FileReader() const handleSignalAbort = () => reader.abort() const cleanUp = () => signal?.removeEventListener("abort", handleSignalAbort) if (signal?.aborted) { reject(new DOMException("The image read was aborted.", "AbortError")) return } signal?.addEventListener("abort", handleSignalAbort, { once: true }) reader.addEventListener( "error", () => { const error = reader.error ?? new Error(`Unable to read "${file.name}".`) cleanUp() reject(error) }, { once: true } ) reader.addEventListener( "abort", () => { cleanUp() reject(new DOMException("The image read was aborted.", "AbortError")) }, { once: true } ) reader.addEventListener("progress", (event) => { if (event.lengthComputable && event.total > 0) { onProgress?.(event.loaded / event.total) } }) reader.addEventListener( "load", () => { const result = reader.result cleanUp() if (typeof result === "string") { resolve(result) } else { reject(new Error(`Unable to read "${file.name}" as a data URL.`)) } }, { once: true } ) try { reader.readAsDataURL(file) } catch (error) { cleanUp() reject(error) } }) }