Receiving an empty string from Chrome FileReader when dealing with large files (300MB or more)

Objective:

  • The task is to read a file from the user's file system as a base64 string in the browser
  • The size of these files can be up to 1.5GB

Challenge:

  • A script that works flawlessly on Firefox, regardless of the file size
  • On Chrome, the script performs well with smaller files (tested with files around 5MB)
  • However, when selecting a larger file (e.g., 400MB), FileReader completes without errors or exceptions but returns an empty string instead of the expected base64 string

Queries:

  • Is this a bug specific to Chrome?
  • Why does it not generate any errors or exceptions despite the issue?
  • What are the potential solutions or workarounds for this problem?

Note:

It's crucial to highlight that chunking is not feasible as the full base64 string needs to be sent via 'POST' to an API that does not support chunks.

Code:

'use strict';

var filePickerElement = document.getElementById('filepicker');

filePickerElement.onchange = (event) => {
  const selectedFile = event.target.files[0];
  console.log('selectedFile', selectedFile);

  readFile(selectedFile);
};

function readFile(selectedFile) {
  console.log('START READING FILE');
  const reader = new FileReader();

  reader.onload = (e) => {
    const fileBase64 = reader.result.toString();

    console.log('ONLOAD','base64', fileBase64);
    
    if (fileBase64 === '') {
      alert('Result string is EMPTY :(');
    } else {
        alert('It worked as expected :)');
    }
  };

  reader.onprogress = (e) => {
    console.log('Progress', ~~((e.loaded / e.total) * 100 ), '%');
  };

  reader.onerror = (err) => {
    console.error('Error reading the file.', err);
  };

  reader.readAsDataURL(selectedFile);
}
<!doctype html>
<html lang="en">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6d0f0202191e191f0c1d2d58435d435d">[email protected]</a>/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">

  <title>FileReader Issue Example</title>
</head>

<body>

  <div class="container">
    <h1>FileReader Issue Example</h1>
    <div class="card">
      <div class="card-header">
        Select File:
      </div>
      <div class="card-body">
        <input type="file" id="filepicker" />
      </div>
    </div>

  </div>

  <script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="67050808131413150617275249574957">[email protected]</a>/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8"
    crossorigin="anonymous"></script>
  <script src="main.js"></script>
</body>

</html>

Answer №1

Is this a chrome bug?

In response to the query posed in Chrome, FileReader API, event.target.result === "", it is important to note that this limitation is not a bug, but rather an intentional constraint within V8 (the JavaScript engine used by Chrome and other platforms). The issue stems from the inability to construct a String exceeding 512MB on 64-bit systems due to V8's heap object limitations, as explained in this commit.

Why is there neither an error nor an exception?

While creating such a large string directly does result in a RangeError, certain operations lack the expected error handling mechanisms. As outlined in FileReader::readOperation Step 3, exceptions should trigger specific actions which are absent in this case.

Detailed steps involving Uint32Array and Blob further illustrate this anomaly, indicating a potential oversight that warrants attention and correction.

I will pursue this matter by raising a relevant issue to address the absence of error handling within the FileReader interface.

How can I fix or work around this issue?

A recommended approach involves modifying your API endpoint to accept binary resources directly instead of relying on data:// URLs, the usage of which is generally discouraged.

An alternative solution for future implementation would entail sending a ReadableStream to your endpoint and performing the data:// URL conversion autonomously using a stream sourced from the Blob.

(Code snippet omitted)

For immediate resolution, consider storing chunks of base64 representations in a Blob, although caution is advised due to potential server-side constraints related to handling excessively large strings. Communication with the API maintainer is strongly suggested to mitigate any issues arising from V8's inherent limitations.

Answer №2

Here is a clever method for converting a blob into chunks and then transforming them into base64 blobs. These base64 chunks are concatenated within a JSON blob along with some pre/suffix JSON parts.

By keeping it as a blob, the browser can efficiently manage memory allocation and even offload it to disk if necessary.

If you adjust the chunkSize to be larger, the browser prefers to store smaller blob chunks in memory (in one bucket).

// Obtaining a sample gradient file (blob)
var canvas=document.createElement("canvas"), context=canvas.getContext("2d"), gradient=context.createLinearGradient(0,0,3000,3000);canvas.width=canvas.height=3000;gradient.addColorStop(0,"red");gradient.addColorStop(1,"blue");context.fillStyle=gradient;context.fillRect(0,0,canvas.width,canvas.height);canvas.toBlob(main);

async function main (blob) {
  var fileReader = new FileReader()
  // It's recommended to add 2 so it omits == from all but the last chunk
  var chunkSize = (1 << 16) + 2 
  var position = 0
  var b64Chunks = []
  
  while (position < blob.size) {
    await new Promise(resolve => {
      fileReader.readAsDataURL(blob.slice(position, position + chunkSize))
      fileReader.onload = () => {
        const base64 = fileReader.result.split(',')[1]
        b64Chunks.push(new Blob([base64]))
        resolve()
      }
      position += chunkSize
    })
  }

  // How you combine all chunks into json is now up to you.
  // This solution just outlines what needs to be done
  // There are more automated ways, but here is a simple form
  // (just a heads-up: this new blob won't create a lot of data in memory, it will only reference other blob locations)
  const jsonData = new Blob([
    '{"data": "', ...b64Chunks, '"}'
  ], { type: 'application/json' })

  /*
  // It's strongly recommended to ask API developers 
  // to implement support for binary/file uploads (multipart-formdata)
  // Base64 is roughly ~33% bigger and handling streaming 
  // this data on the server to disk is nearly impossible 
  fetch('./upload-files-to-bad-json-only-api', {
    method: 'POST',
    body: jsonData
  })
  */
  
  // Just testing that it still functions
  //
  // new Response(jsonData).json().then(console.log)
  fetch('data:image/png;base64,' + await new Blob(b64Chunks).text()).then(response => response.blob()).then(blob => console.log(URL.createObjectURL(blob)))
}

I opted not to use

base64 += fileReader.result.split(',')[1]
and JSON.stringify since dealing with GiB of data is substantial and JSON isn't ideal for handling binary data.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When going through an array using jquery, only the final object is returned

I am diving into the world of jQuery and dealing with what seems to be a basic issue. <input type="text" name="text1" value=""></input> <input type="text" name="text2" value=""></input> <input type="text" name="text3" value=""&g ...

Is there a way to leverage JavaScript to click on one div and modify the settings of another div simultaneously?

I am struggling with my code which has unnecessary information. <div> <div id="one" class="button"></div> <div id="two" class="button"></div> </div> <div> <div class="Home tab"> < ...

Creating a dynamic user interface in Angular 6 that successfully tracks changes without reliance on the parent

As I delve into the world of Angular, I am faced with a challenge in creating a reusable component that will be bundled into an npm module. The issue lies in the UI change detection aspect. In order for the component to function properly, the application ...

Tips for accessing the HTML content enclosed within a specific HTML tag using Python with Selenium

I am trying to extract the source code of an HTML document that is embedded within an <iframe> tag generated by JavaScript. The HTML contents within this <iframe> tag appears as #document, which expands to reveal a full HTML document starting w ...

Error Encountered when Attempting to Retry AI SDK on Vercel's

I've been implementing code from the official AI SDK website but unfortunately, the code is not functioning properly. The error message I'm encountering is: RetryError [AI_RetryError]: Failed after 3 attempts. Last error: Failed to process error ...

ChartJS does not function properly when included in a .php file that is loaded using jQuery's .load() method (

I am facing an issue with this plugin not working when I use the jQuery ajax function .load(). Below is the snippet of my PHP code: <script src="../Chart.js"></script> <script type="text/javascript" src="../functions.js"></script> ...

Utilizing AngularJS to bind form fields with select boxes to enable synchronized data. Modifying the selection in the dropdown should dynamically

Currently, I am working on an input form that involves a select with various options. Depending on the user's selection, three additional fields need to be populated accordingly. For instance: If the user picks Option1, then the three other fields s ...

Select a Date: Input for Date Selection

Is it possible to restrict the selection of certain days using HTML date input validation? Some booking websites have a feature where an interactive calendar only allows users to select specific dates for events, while others are greyed out and cannot be c ...

The hydration error in next js is causing this code to malfunction

Why am I encountering a hydration error with this code in NextJS? The Items variable is an array of ReactNode's. Any suggestions for an alternative approach? I've searched extensively for information but haven't found anything related to Nex ...

The function window.location.reload(true) is failing to properly refresh the page

Currently, I have a PHP page that retrieves data from a MYSQL database and creates HTML content based on the returned rows. I recently implemented a feature where clicking a button adds a new row to the database using AJAX, jQuery, and PHP. However, after ...

Tips on enabling JS tooltips in Shadow DOM

I am currently developing an app using Vue and Bootstrap, where I am creating web components based on the official Vue documentation. The Bootstrap framework and my business logic are functioning well within the #shadow-root of the web components, behaving ...

Issue encountered while performing an Upsert operation on Pinecone using Node.js

Oops! PineconeArgumentError: The argument provided for upsert contains type errors: the argument should be an array. Package "@pinecone-database/pinecone": "^1.0.0", Inquiry const index = pinecone.Index(process.env.PINECONE_INDEX_NAME ...

The dynamic data graph generated by HIGHCHARTS Areaspline is not as effective as expected

I need help creating a Dynamic Areaspline graph, but the result is coming out strangely. Does anyone have any ideas on how to fix this and get a smooth series? Here is an example of the issue: http://jsfiddle.net/mchc59nb/1/ chart: { ...

The Google reCaptcha reply was "Uncaught (in promise) null"

When using reCaptcha v2, I encountered an issue in the developer console showing Uncaught (in promise) null message regardless of moving the .reset() function. Here is the console output: https://i.stack.imgur.com/l24dC.png This is my code for reCaptcha ...

The image will remain static and will not alternate between hidden and visible states

I am facing a challenge trying to toggle an image between 'hidden' and 'show' My approach is influenced by the post on How to create a hidden <img> in JavaScript? I have implemented two different buttons, one using html and the ...

Is it possible to change the name of a PHP file using the value entered in an input

I am currently in the process of trying to change the name of a file while uploading it using the JQuery uploader. I have made some progress, and here is the crucial part of my UploadHandler.php: protected function handle_file_upload($uploaded_file, $name ...

Issues with JQuery `.click()` event

Check out this snippet of code I'm working with: $(".item").click(function () { alert("clicked!"); }); I also have (hypothetically; in reality it's more complex) the following HTML on my page: <a href="#" class="item"> ...

SyntaxError: Unexpected symbol

I have an issue with the following code: let op = data.map(({usp-custom-90})=> usp-custom-90 ) When I run it, I encounter the following error: Uncaught SyntaxError: Unexpected token - I attempted to fix it by replacing the dash with –, but t ...

JavaScript: Filtering an object array pushed from a MySQL query

After retrieving an array from mysql, I encountered a problem while trying to filter out certain objects. var notes = [] db.query('SELECT * FROM test WHERE working = 0') .on('result', function(data){ ...

Which specific jQuery functionality was made visible?

When it comes to jQuery, how is it exposed? // Expose jQuery to the global object window.jQuery = window.$ = jQuery; However, there are in fact two versions of jQuery: var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( s ...