73

I would like to read a file and convert it into a base64 encoded string using the FileReader object. Here's the code I use:

var reader = new FileReader();
reader.onloadend = function(evt) {  
    // file is loaded
    result_base64 = evt.target.result; 
};
reader.readAsDataURL(file);

But in this case, I get the result of the conversion in the event handler (onloadend event). I would like a synchronous method. Is there a way the readAsDataURL method can return the value of the result_base64 variable directly?

5
  • 3
    "I would like a synchronous method." Any particular reason why? I don't think it's possible. Commented Jun 12, 2013 at 14:54
  • firefox has/had the file.getAsDataURL() method, but it's deprecated and was the only sync version of any browser, afaik. Commented Jun 12, 2013 at 15:15
  • I store the paths in a local storage database, I need to send the images to a server afterwards (with a loop on all images, this is why I need a synchronous method). I would like to avoid storing base64 strings in the database in order not to exceed the local storage limit... Commented Jun 12, 2013 at 15:17
  • But you could implement the loop by e.g. calling the upload for the next one in the completion callback for the previous image Commented Jun 12, 2013 at 16:06
  • I wanted something more reusable that could take a file as an input and return the base64 encoded string as an output but it seems to be impossible... Thank you for your help! Commented Jun 13, 2013 at 18:45

8 Answers 8

50

You can use the standard FileReaderSync, which is a simpler, synchronous, blocking version of the FileReader API, similar to what you are already using:

let reader = new FileReaderSync();
let result_base64 = reader.readAsDataURL(file); 

console.log(result_base64); // aGV5IHRoZXJl...

Keep in mind though that this is only available in worker threads, for obvious reasons.


If you need a solution for the main thread that "reads like" a synchronous API, i.e. sequentially, you can wrap the async FileReader in a promise and use async functions (you might need to transpile):

async function readFileAsDataURL(file) {
    let result_base64 = await new Promise((resolve) => {
        let fileReader = new FileReader();
        fileReader.onload = (e) => resolve(fileReader.result);
        fileReader.readAsDataURL(file);
    });

    console.log(result_base64); // aGV5IHRoZXJl...

    return result_base64;
}

And then you can either await this function in another async context:

async function main() {
    let file = new File(...)
    let dataURL = await readFileAsDataURL(file)
    console.log(dataURL); // aGV5IHRoZXJl...
}

... or just consume it using promise callbacks (doesn't need an async context):

readFileAsDataURL(file).then(dataURL => {
    console.log(dataURL); // aGV5IHRoZXJl...
});
Sign up to request clarification or add additional context in comments.

4 Comments

This solution is aesthetically one of the better but keep in mind that readFileAsDataURL will go to the event loop so if you call it from another piece of synchronise code you need to await it there aswell. Otherwise it will return the pending promise.
yes, maybe worth noting, if you call it from other function, it should be async as well and call this with await keyword, otherwise it will just return "promise object".
Where is 'FileReaderSync' defined? I am getting error
@sgowd Did you try in a web worker? It won't work on the main thread.
19

Synchronous tasks (blocking) are generally bad. If there is no real reason to do that synchronously, I strongly recommend you to use the event callback.

Imagine your file is broken and the HTML5 api cant read, it wont give you the result. It would break your code and block the site. Or, someone could select a 10GB file, which would freeze your HTML page until the file is completely loaded. With that asynchronous event handler you are able to catch possible errors.

To work around limitations with callbacks, i use a simple trick:

var ready = false;
var result = '';

var check = function() {
    if (ready === true) {
         // do what you want with the result variable
         return;
    }
    setTimeout(check, 1000);
}

check();

var reader = new FileReader();
reader.onloadend = function(evt) {
    // file is loaded
    result = evt.target.result;
    
    ready = true;
};
reader.readAsDataURL(file);

the check function, checks every second if the ready flag variable is set to true. If so, you can be sure the result is available.

It may not be best practice to do so, but i made a webapp using this technique about 30 times with more than 10 setTimeouts at the same time running, and experienced no problem until now.

7 Comments

Had the exact same problem as i wanted to pass parameter variables to the callback. Purpose was to split the file into chunks so i could send it chunk-wise to the server. Closures are a great thing once understanded.
stackoverflow.com/a/46832928/851951 this approach is good to, but it remains working asynchronously.
"Synchronous tasks (blocking) are generally bad" - false. While true for common specific environments, like those associated with web development, it is not generally bad. (e.g. a single-threaded non-web, non-UI dedicated application on embedded hardware)
Of course you're right, but since this question was about javascripts html5 filereader we can safely assume that we are talking about a browser environment.
This is NOT a synchronous task.
|
7

As of 01.11.2022, file.getAsDataURL() is obsolete, which was the only direct synchronous method. Simply pass the File object to the following function, and the function will return a data url.

readSyncDataURL=function(file){
var url=URL.createObjectURL(file);//Create Object URL
var xhr=new XMLHttpRequest();
xhr.open("GET",url,false);//Synchronous XMLHttpRequest on Object URL
xhr.overrideMimeType("text/plain; charset=x-user-defined");//Override MIME Type to prevent UTF-8 related errors
xhr.send();
URL.revokeObjectURL(url);
var returnText="";
for (var i=0;i<xhr.responseText.length;i++){
returnText+=String.fromCharCode(xhr.responseText.charCodeAt(i)&0xff);};//remove higher byte
return "data:"+file.type+";base64,"+btoa(returnText);}//Generate data URL

The next function is just a general function converting a File object to a binary string(in case someone is searching for it):

readSyncBinaryString=function(file){
var url=URL.createObjectURL(file);//Create Object URL
var xhr=new XMLHttpRequest();
xhr.open("GET",url,false);//Synchronous XMLHttpRequest on Object URL
xhr.overrideMimeType("text/plain; charset=x-user-defined");//Override MIME Type to prevent UTF-8 related errors
xhr.send();
URL.revokeObjectURL(url);
var returnText="";
for (var i=0;i<xhr.responseText.length;i++){
returnText+=String.fromCharCode(xhr.responseText.charCodeAt(i)&0xff);};//remove higher byte
return returnText;}

If you need an ArrayBuffer use this function:

readSyncArrayBuffer=function(file){
var url=URL.createObjectURL(file);
var xhr=new XMLHttpRequest();
xhr.open("GET",url,false);//Synchronous XMLHttpRequest on Object URL
xhr.overrideMimeType("text/plain; charset=x-user-defined");//Override MIME Type to prevent UTF-8 related errors
xhr.send();
URL.revokeObjectURL(url);
var returnArray=[];
for (var i=0;i<xhr.responseText.length;i++){
returnArray.push(xhr.responseText.charCodeAt(i)&0xff);};//remove higher byte
return new Uint8Array(returnArray).buffer;}//Generate Buffer

8 Comments

That is interesting. I tried that and the DataURL one seems to work. When I tried the Binary String to submit and then store as a BLOB in my DB I must have some sort of encoding issue because it seems like there is some data corruption in that case. The DataUrl's are bigger, but in some cases actually easier to deal with.
@SScotti i don't know what type of DB you are using, but it might be a good idea to base64-encode the Binary String before storing it in your DB, as a binary string can, depending on the used file, contain characters called "control characters", which might be the cause of your trouble. It is what the DataURL function does. It base64-encodes the binary string before returning it. (and adds a content-type to it). In this case: btoa(readSyncBinaryString(file)).
Yep. The extra is probably worth it to avoid issues. I did kind of want to use Promises and a call back, but it takes basically less than a second to run your function for the files that I am using.
Where do I get a file object from? All I have is a file name (URL)
@Kaiido in order to synchronously recieve data from somewhere (here the Object URL), you have to use synchronous HTTP requests (by passing false to xhr.open()). However, when a request is synchronous, you cannot set responseType. Doing so results in an InvalidAccessError. Therefore, it is mandatory to process it from the default response type (text) to the desired type.
|
0

The below code works sync way to read the file

 function SyncFileReader(file) {
    let self = this;
    let ready = false;
    let result = '';

    const sleep = function (ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    self.readAsArrayBuffer = async function() {
        while (ready === false) {
          await sleep(100);
        }
        return result;
    }    

    const reader = new FileReader();
    reader.onloadend = function(evt) {
        result = evt.target.result;
        ready = true;
    };
    reader.readAsArrayBuffer(file);
  }

Usage :

const fileReader = new SyncFileReader(file);
const arrayBuffer = await fileReader.readAsArrayBuffer();

5 Comments

A nice answer, but you did not helped him with the issue of: convert it into a base64 encoded string. And also i will be great to elaborate a little more why did you do each step.
If you replace the line reader.readAsArrayBuffer(file); with reader.readAsDataURL(file); in SyncFileReader then it will return the content of the file as a string
You are using the await keyword and your code is therefore not synchronous.
@sboisse await certify that fileReader.readAsArrayBuffer() is completed before executing the rest of the code, so... in the end this is the excpected synchronous behavior, isn't it ?
@NicolasDavid I think you need to read about what is synchronous vs asynchronous code. Using the await keyword intrinsically means the code is ASYNCHRONOUS. Code that needs to wait for a promise to complete before executing something else is exactly what asynchronous coding is about. In synchronous coding, no promise can be awaited on them, and the result of your task is computed right away.
0

I stumbled upon this thread because I was looking for a straight forward way to wait until the File was read in an async function. Here is my solution:

    const reader = new FileReader()

    function readFile (file) {
        return new Promise((resolve, reject) => {
            reader.onload = (event) => {
                resolve(event.target.result)
            }
            reader.onerror = (event) => {
                reject(event.target.error)
            }
            reader.readAsArrayBuffer(file) // change this if you want to read the file as e.g. text
        })
    }

The usage is then as straight forward:

async function whatever() {
    let content = await readFile(your_file)
}

Happy hexin'

Comments

-2

To read the contents of a file synchronously use fs.readFileSync

var fs = require('fs');
var content = fs.readFileSync('myfilename');
console.log(content);

fs.createReadStream creates a ReadStream.

2 Comments

This seems node.JS specific, it's not in any browser, for any others being wrong footed by the answer.
Thanks very much thats exactly what i needed for nodejs server :)
-3

The below code works sync way to read the file and return its content as text (string)

 function SyncFileReader(file) {
    let self = this;
    let ready = false;
    let result = '';

    const sleep = function (ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    self.readAsDataURL = async function() {
        while (ready === false) {
          await sleep(100);
        }
        return result;
    }    

    const reader = new FileReader();
    reader.onloadend = function(evt) {
        result = evt.target.result;
        ready = true;
    };
    reader.readAsDataURL(file);
  }

Usage :

const fileReader = new SyncFileReader(file);
const arrayBuffer = await fileReader.readAsDataURL();

1 Comment

How can you say it works sync when you use async await? It literally says its async :)
-4

In Node.js, Use execSync from child_process and have the shell read it in for you synchronously. Redirect the output of this child process to the parent.

// Don't forget to use your favorite encoding in toString()
var execSync = require('child_process').execSync;
var fileContents = execSync('cat path/to/file.txt', {stdio: "pipe"}).toString();

I'll gladly accept your nomination for the UUOC award. ;)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.