From f6ce8611b2b173c65859192c7b168413071a5f26 Mon Sep 17 00:00:00 2001 From: Matchu Date: Sun, 17 Jan 2021 04:42:35 -0800 Subject: [PATCH] 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 :) --- src/app/WardrobePage/OutfitControls.js | 17 +++++++++++++++-- src/app/util.js | 3 ++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/WardrobePage/OutfitControls.js b/src/app/WardrobePage/OutfitControls.js index aa4ba74..c6287fb 100644 --- a/src/app/WardrobePage/OutfitControls.js +++ b/src/app/WardrobePage/OutfitControls.js @@ -407,6 +407,7 @@ function ControlButton({ icon, "aria-label": ariaLabel, ...props }) { function useDownloadableImage(visibleLayers) { const [downloadImageUrl, setDownloadImageUrl] = React.useState(null); const [preparedForLayerIds, setPreparedForLayerIds] = React.useState([]); + const toast = useToast(); const prepareDownload = React.useCallback(async () => { // Skip if the current image URL is already correct for these layers. @@ -426,7 +427,19 @@ function useDownloadableImage(visibleLayers) { 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 context = canvas.getContext("2d"); @@ -444,7 +457,7 @@ function useDownloadableImage(visibleLayers) { ); setDownloadImageUrl(canvas.toDataURL("image/png")); setPreparedForLayerIds(layerIds); - }, [preparedForLayerIds, visibleLayers]); + }, [preparedForLayerIds, visibleLayers, toast]); return [downloadImageUrl, prepareDownload]; } diff --git a/src/app/util.js b/src/app/util.js index 0eb52f1..32a3d22 100644 --- a/src/app/util.js +++ b/src/app/util.js @@ -277,7 +277,8 @@ export function loadImage({ src, crossOrigin = null }) { const image = new Image(); const promise = new Promise((resolve, reject) => { image.onload = () => resolve(image); - image.onerror = (e) => reject(e); + image.onerror = () => + reject(new Error(`Failed to load image: ${JSON.stringify(src)}`)); if (crossOrigin) { image.crossOrigin = crossOrigin; }