Clearer errors when image download fails

Two fixes in here, for when image downloads fail!

1) Actually catch the error, and show UI feedback
2) Throw it as an actual exception, so the console message will have a stack trace

Additionally, debugging this was a bit trickier than normal, because I didn't fully understand that the image `onerror` argument is an error _event_, not an Error object. So, Sentry captured the uncaught promise rejection, but it didn't have trace information, because it wasn't an Error. Whereas now, if I forget to catch `loadImage` calls in the future, we'll get a real trace! both in the console for debugging, and in Sentry if it makes it to prod :)
This commit is contained in:
Emi Matchu 2021-01-17 04:42:35 -08:00
parent cfb5504341
commit f6ce8611b2
2 changed files with 17 additions and 3 deletions

View file

@ -407,6 +407,7 @@ function ControlButton({ icon, "aria-label": ariaLabel, ...props }) {
function useDownloadableImage(visibleLayers) { function useDownloadableImage(visibleLayers) {
const [downloadImageUrl, setDownloadImageUrl] = React.useState(null); const [downloadImageUrl, setDownloadImageUrl] = React.useState(null);
const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]); const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]);
const toast = useToast();
const prepareDownload = React.useCallback(async () => { const prepareDownload = React.useCallback(async () => {
// Skip if the current image URL is already correct for these layers. // Skip if the current image URL is already correct for these layers.
@ -426,7 +427,19 @@ function useDownloadableImage(visibleLayers) {
loadImage(getBestImageUrlForLayer(layer)) loadImage(getBestImageUrlForLayer(layer))
); );
const images = await Promise.all(imagePromises); let images;
try {
images = await Promise.all(imagePromises);
} catch (e) {
console.error("Error building downloadable image", e);
toast({
status: "error",
title: "Oops, sorry, we couldn't download the image!",
description:
"Check your connection, then reload the page and try again.",
});
return;
}
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const context = canvas.getContext("2d"); const context = canvas.getContext("2d");
@ -444,7 +457,7 @@ function useDownloadableImage(visibleLayers) {
); );
setDownloadImageUrl(canvas.toDataURL("image/png")); setDownloadImageUrl(canvas.toDataURL("image/png"));
setPreparedForLayerIds(layerIds); setPreparedForLayerIds(layerIds);
}, [preparedForLayerIds, visibleLayers]); }, [preparedForLayerIds, visibleLayers, toast]);
return [downloadImageUrl, prepareDownload]; return [downloadImageUrl, prepareDownload];
} }

View file

@ -277,7 +277,8 @@ export function loadImage({ src, crossOrigin = null }) {
const image = new Image(); const image = new Image();
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
image.onload = () => resolve(image); image.onload = () => resolve(image);
image.onerror = (e) => reject(e); image.onerror = () =>
reject(new Error(`Failed to load image: ${JSON.stringify(src)}`));
if (crossOrigin) { if (crossOrigin) {
image.crossOrigin = crossOrigin; image.crossOrigin = crossOrigin;
} }