react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Bug with FileReader `.readAsDataURL` method when inside a promise.

Open nicolasmelo1 opened this issue 3 years ago • 1 comments

Description

Hello,

FileReader is presenting some issues and cannot be effectively used in a RN project.

So i've been trying to work with FileReader API on react native (with Expo) and it's not working as expected:

What i'm trying to reproduce is a file upload with octet-stream like:

Reference: https://www.youtube.com/watch?v=Ix-c2X7dlks (source code: https://github.com/hnasr/javascript_playground/tree/master/simple-uploader) And: https://www.youtube.com/watch?v=dbYBVbrDnwg (source code: https://github.com/dejwid/mern-chunked-upload) # specially this one since React Native does not support readAsArrayBuffer().

The code is this one:

export default function useUploadAttachment() {
  const CHUNK_SIZE = 1024 * 1000;
  const isSuccess = (code: number) => code >= 200 && code <= 299;

  function onUploadAttachmentFile(blob: Blob) {
    const fileUUID = uuid();

    return new Promise(function (resolve, reject) {
      const fileSize = blob.size;
      const fileName = (blob as any).name;
      const TOTAL_CHUNKS = Math.ceil(fileSize / CHUNK_SIZE);

      function batchUpload(currentChunkIndex = 0) {
        const fileReader = new FileReader();

        const isLastChunk = currentChunkIndex === TOTAL_CHUNKS - 1;
        const from = currentChunkIndex * CHUNK_SIZE;
        const to = from + CHUNK_SIZE;
        const blobFile = blob.slice(from, to);
        let isSending = false;
        const timeout = setTimeout(() => {
          if (isSending === false) {
            fileReader.abort();
            batchUpload(currentChunkIndex);
          }
        }, 1000);

        fileReader.onload = (e) => {
          isSending = true;
          const dataToUpload = e.target?.result;
          if (dataToUpload) {
            const parameters = new URLSearchParams({
              name: fileName,
              size: fileSize.toString(),
              uuid: fileUUID,
              currentChunkIndex: currentChunkIndex.toString(),
              totalChunks: TOTAL_CHUNKS.toString(),
            });
            api
              .post(
                '/attachments/upload?' + parameters,
                {
                  headers: {
                    'Content-Type': 'application/octet-stream',
                  },
                  data: dataToUpload,
                },
                {
                  isAuthenticated: true,
                },
              )
              .then((response) => {
                clearTimeout(timeout);
                if (response && isSuccess(response.response.status)) {
                  if (isLastChunk) resolve(response);
                  else batchUpload(currentChunkIndex + 1);
                } else {
                  resolve(response);
                }
              })
              .catch((error) => {
                clearTimeout(timeout);
                reject(error);
              });
          } else {
            console.log('error on index ', currentChunkIndex);
            batchUpload(currentChunkIndex);
          }
        };
        fileReader.readAsDataURL(blobFile);
      }

      batchUpload();
    });
  }

  return {
    onUploadAttachmentFile,
  };
}

So what it does is this: 1 - We recieve a blob by doing it like this:

  const response = await fetch(photoUri); // photoUri is the uri of the photo received from the image picker or when i take a photo.
  const blob = await response.blob();

2 - After that we call the function by

  const { onUploadAttachmentFile } = useAttachment();

function onContinue() {
   // ... code for fetching blob from uri
    const response = await onUploadAttachmentFile(blob);
}

3 - response should be the response of the last request. So we can do stuff with it.

4 - The problem is that FileReader does not work by any way on iOS.

5 - The fileReader.onload is called N times, after that it just is not called anymore.

6 - On web the same api works as expected, the problem relies on react-native, and more on iOS.

I've tried to hack my way around this issue, that's why have added the

const timeout = setTimeout(() => {
  if (isSending === false) {
    fileReader.abort();
    batchUpload(currentChunkIndex);
 }
}, 1000);

I thought that by doing this, if for some reason filereader is not called i could just rerun the function again and i would get the file content. Turns out FileReader API just work for some time and suddenly it stops working.

For android i've been testing on my device, and it looks like this API works as expected.

On some cases the app also closes: https://user-images.githubusercontent.com/12915742/195933531-881411a9-2d92-4f69-9b62-876f4eb0162e.mov

this is the piece of code that is making the app close:

const response = await fetch(photoUri);
const blob = await response.blob();
const blobSize = blob.size;
const TOTAL_CHUNKS = Math.ceil(blobSize / CHUNK_SIZE);

for (let currentChunkIndex = 0; currentChunkIndex < TOTAL_CHUNKS; currentChunkIndex++) {
  console.log(currentChunkIndex);
  const fileReader = new FileReader();
  const from = currentChunkIndex * CHUNK_SIZE;
  const to = from + CHUNK_SIZE;
  const blobFile = blob.slice(from, to);

  fileReader.onload = (e) => {
    console.log((e.target?.result as string).length);
  };
  fileReader.readAsDataURL(blobFile);

Version

0.69.6

Output of npx react-native info

System: OS: macOS 12.6 CPU: (8) arm64 Apple M1 Memory: 150.83 MB / 16.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 16.13.0 - ü/.nvm/versions/node/v16.13.0/bin/node Yarn: 1.22.17 - /opt/homebrew/bin/yarn npm: 8.1.0 - ü/.nvm/versions/node/v16.13.0/bin/npm Watchman: Not Found Managers: CocoaPods: 1.11.3 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.4, iOS 16.0, macOS 12.3, tvOS 16.0, watchOS 9.0 Android SDK: Not Found IDEs: Android Studio: Bumblebee 2021.1.1 Beta 4 Bumblebee 2021.1.1 Beta 4 Xcode: 14.0.1/14A400 - /usr/bin/xcodebuild Languages: Java: 18.0.2 - /usr/bin/javac npmPackages: Äreact-native-community/cli: Not Found react: Not Found react-native: Not Found react-native-macos: Not Found npmGlobalPackages: react-native: Not Found info React Native v0.70.3 is now available (your project is running on v0.69.6). info Changelog: https://github.com/facebook/react-native/releases/tag/v0.70.3. info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.69.6. info To upgrade, run "react-native upgrade".

Steps to reproduce

  • Create a function in a new react native app.

  • This function should recieve a uri

  • When a uri is recieved you should convert to a blob.

  • Slice the contents of the blob in chunks of size N. This will create N blobs.

  • Now we loop through each Chunk of the blob, each time creating a new FileReader object.

  • We will loop inside of a Promise.

  • Now you will see that onload or onloadend will be called N times and it'll just stop working.

Snack, code example, screenshot, or link to a repository

https://snack.expo.dev/@nicolasmelolc/nervous-pretzels

You see that when we run, we loop correctly but the promise does not resolve correctly. I don't know why this happens.

I've also tried taking the FileReader out from the Promise. Sometimes it works, sometimes the app just closes and i don't know why.

I've tried with both onloadend and onload, both give the same error.

nicolasmelo1 avatar Oct 14 '22 20:10 nicolasmelo1

I have the same issue

fmcalado avatar Oct 18 '22 03:10 fmcalado

This is related to #35959 (fix #35971). Enforce the requirement that the end parameter does not exceed the blob size. For the snack, change line 20 to

        const to = Math.min(blob.size, from + CHUNK_SIZE);

SheetJSDev avatar Jan 26 '23 01:01 SheetJSDev

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Aug 01 '23 05:08 github-actions[bot]

This issue was closed because it has been stalled for 7 days with no activity.

github-actions[bot] avatar Aug 13 '23 05:08 github-actions[bot]