p5.js loadImage() ↔ Browser Image Loading: How to Gracefully Cancel or Time‑Out Asynchronous Loads?
0 reputation · 16 Feb 2023, 09:35 UTC
0 reputation · 16 Feb 2023, 09:35 UTC
When an image is requested with loadImage(), p5.js internally creates an HTMLImageElement and attaches onload and onerror callbacks. The library offers a timeout argument, but this merely stops the callback from firing after a set period; it does not abort the underlying network request or clear the image source. No public API exists to cancel the load mid‑stream, and developers must resort to manual workarounds such as setting nativeElement.src = '' or using an AbortController with a custom fetch implementation. This lack of a standardized cancellation mechanism is an unresolved decision in the p5.js ecosystem.
Given the growing need for responsive sketches that can abort heavy image loads or enforce strict time limits, the community is debating whether p5.js should expose a cancellable promise or integrate AbortController natively. The current documentation does not cover this scenario, leaving developers uncertain about the best practice.
What are the implications of adding a built‑in abort API to loadImage()?
How can a sketch reliably cancel a pending image load without triggering onload or onerror callbacks?
What would be the most browser‑compatible approach to enforce a timeout that also cancels the underlying request?
35275 reputation · 16 Feb 2023, 21:33 UTC
You cannot truly cancel an in-flight loadImage() call, because p5.js loads images through a browser HTMLImageElement, and the web platform offers no API to abort an image request once it starts. What you can do reliably is (1) stop your sketch from reacting to a late result, and (2) stop waiting after a deadline. If you need the network request itself to actually abort, bypass loadImage() and use fetch() with an AbortController.
Confirmed behavior: loadImage(path, success, error) creates an Image, sets handlers, and assigns src. The browser then owns the request. There is no spec-level cancel for that request, and p5.js exposes no abort handle.
Workaround, not guaranteed: setting img.src = '' (or a tiny data URI) causes some browsers to drop the fetch, but this varies by engine and version. Treat it as a hint, not a cancellation mechanism — verify in DevTools' Network tab before relying on it.
If the goal is "don't let a stale image clobber the current one," a monotonically increasing id is enough:
let loadToken = 0;
let currentImg = null;
function requestImage(url) {
const myToken = ++loadToken;
loadImage(url,
img => { if (myToken === loadToken) currentImg = img; },
err => { if (myToken === loadToken) console.warn('failed', err); }
);
}
Any load that finishes after a newer request started is silently discarded. Neither callback fires user-visible side effects for stale loads, which answers the "cancel without triggering handlers" concern in practice.
function loadImageWithTimeout(url, ms) {
const load = new Promise((resolve, reject) =>
loadImage(url, resolve, reject));
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), ms));
return Promise.race([load, timeout]);
}
Important caveat: the timeout only stops your code from waiting. The underlying request continues in the background and the success callback may still fire later — combine this with the token pattern if that matters.
When bytes on the wire actually matter (slow connections, large assets, rapid switching), load the bytes yourself:
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
const res = await fetch(url, { signal: controller.signal });
const blob = await res.blob();
clearTimeout(timer);
const objectUrl = URL.createObjectURL(blob);
loadImage(objectUrl, img => {
// use img, then release memory when replaced:
// URL.revokeObjectURL(objectUrl);
});
This is the most browser-compatible way to both time out and cancel: AbortController is supported in all modern browsers, and the request visibly aborts in the Network panel. Remember to revoke object URLs to avoid leaking memory.
Yes, but the honest design is a fetch-based loader under the hood, because a cancellable promise wrapping HTMLImageElement would still be cosmetic — it could detach handlers but not stop the download. A native API would also need to define behavior for preload(), where a hung load currently stalls sketch startup; moving optional assets into setup() with manual loading already avoids that stall today.
loadImage error-callback signature against the reference for the p5.js version you actually ship — it has changed across releases.Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 16 Feb 2023, 16:26 UTC
While the token-based approach effectively prevents stale images from updating the sketch, it is important to distinguish between application-level cancellation and network-level abortion. When using loadImage() with a timeout or a token, the JavaScript callback is ignored, but the browser's network thread often continues the download in the background until the resource is fully retrieved or the connection expires.
For sketches loading high-resolution assets over slow connections, verifying the Network tab in DevTools is essential. If requests remain "Pending" long after your timeout triggers, bypassing loadImage() in favor of fetch() and AbortController is the only way to truly free the network socket.