0

I need to return the getThumbnail function so my images get resized. My problem now is that when I tried the code below, all the payload assigned to dispatch returns a promise now. How can I get rid of that and just return a normal payload. Before I had no problem returning it without the getThumbnail but right now I'm forced since getThumbnail is a promise. How do I fix this? Do I adjust getThumbnail function or?

The issue on this code:

            const responseImages = response?.data?.map(async (image) => {
                return {
                    productName: image?.uploadResult?.productTitle,
                    imageFile: await getThumbnail(
                        images.find((img) => img?.imageFileName === image?.mediaCode)
                        ?.imageFile || null, {
                            thumbWidth: 100,
                            thumbHeight: 100,
                            bgColor: "black",
                        }
                    ),
                    imageFileName: image?.mediaCode || "",
                };
            });

helpers.js

const loadImage = (src) =>
  new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = () => resolve(img)
    img.onerror = reject
    img.src = src
  })

const resize = (imageWidth, imageHeight, thumbWidth, thumbHeight) => {
  let w = 0,
    h = 0,
    x = 0,
    y = 0,
    widthratio = imageWidth / thumbWidth,
    heightratio = imageHeight / thumbHeight,
    maxratio = Math.max(widthratio, heightratio)
  if (maxratio > 1) {
    w = imageWidth / maxratio
    h = imageHeight / maxratio
  } else {
    w = imageWidth
    h = imageHeight
  }
  x = (thumbWidth - w) / 2
  y = (thumbHeight - h) / 2
  return { w: w, h: h, x: x, y: y }
}

export async function getThumbnail(
  photo,
  { thumbWidth, thumbHeight, bgColor }
) {
  const img = await loadImage(URL.createObjectURL(photo))
  const { naturalWidth, naturalHeight } = img

  const canvas = document.createElement('canvas')
  const cx = canvas.getContext('2d')

  canvas.width = thumbWidth
  canvas.height = thumbHeight

  const dimensions = resize(
    naturalWidth,
    naturalHeight,
    thumbWidth,
    thumbHeight
  )

  cx.fillStyle = bgColor
  cx.fillRect(0, 0, thumbWidth, thumbHeight)

  cx.drawImage(img, dimensions.x, dimensions.y, dimensions.w, dimensions.h)

  return canvas.toDataURL('image/jpeg')
}

Action.js

export const uploadPhotos =
    ({
        photos,
        size,
        controller
    }) =>
    async (dispatch) => {
        // Identify the part of the file name that excludes the (optional) sequence number
        const keyedphotos = photos.map((photo) => [
            photo.imageFileName.replace(/-\w+\.\w+$/, ""),
            photo,
        ]);
        // Create a group for each such prefix
        const map = new Map(keyedphotos.map(([key]) => [key, []]));
        // Populate those groups
        for (const [key, photo] of keyedphotos) map.get(key).push(photo);
        // And extract those groups into an array (of subarrays)
        const photoGroups = [...map.values()];
        let index = 0; // Keep the index counter separate

        photoGroups.forEach(async (photos) => {
            // Iterate the groups that can run in parallel
            for (const photo of photos) {
                // Iterate the photos that should run sequentially
                const id = index++; // Get the next unique id
                const formData = new FormData();
                formData.append(photo?.imageFileName, photo?.imageFile);

                dispatch({
                    type: constants.UPLOAD_PHOTOS_START,
                    size
                });
                try {
                    const response = await axios.post(
                        `${API_URL}/photos/upload`,
                        formData, {
                            onUploadProgress({
                                loaded,
                                total
                            }) {
                                dispatch(setUploadProgress({
                                    id,
                                    loaded,
                                    total
                                })); // Use id
                            },
                            signal: controller.signal,
                        }
                    );

                    dispatch({
                        type: constants.UPLOAD_PHOTOS_SUCCESS,
                        payload: response.data,
                    });

                    const responseImages = response?.data?.map(async (image) => {
                        return {
                            productName: image?.uploadResult?.productTitle,
                            imageFile: await getThumbnail(
                                images.find((img) => img?.imageFileName === image?.mediaCode)
                                ?.imageFile || null, {
                                    thumbWidth: 100,
                                    thumbHeight: 100,
                                    bgColor: "black",
                                }
                            ),
                            imageFileName: image?.mediaCode || "",
                        };
                    });
                } catch (error) {
                    dispatch({
                        type: constants.UPLOAD_PHOTOS_FAILURE,
                        payload: error,
                    });
                }
            }
        });
    };
Joseph
  • 7,042
  • 23
  • 83
  • 181

1 Answers1

0

I assume that your response.data is truly an array, so you can use the map function.

Here, the problem falls into the function you provided for the map.

The async...await function actually returns a Promise, not the resolved object.

So instead, for each item of your data, you get "a Promise to resolve data, not the data themselves". The result of this data mapping is an array of Promises.

Fortunately, you are just 1 more step to achieving what you want.

Just wrap your code into Promise.all(), since the all(...) accepts an array of Promises and returns to you the resolved data.

           const responseImages = await Promise.all(response?.data?.map(async (image) => {
                return {
                    productName: image?.uploadResult?.productTitle,
                    imageFile: await getThumbnail(
                        images.find((img) => img?.imageFileName === image?.mediaCode)
                        ?.imageFile || null, {
                            thumbWidth: 100,
                            thumbHeight: 100,
                            bgColor: "black",
                        }
                    ),
                    imageFileName: image?.mediaCode || "",
                };
            }));

Finally, depends on situation, you might consider Promise.allSettled(), Promise.any(), Promise.race().

Read more about these standard functions/methods: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Khoa
  • 2,632
  • 18
  • 13