64

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 directly the value of the 'result_base64' variable ?

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Laila
  • 1,421
  • 3
  • 13
  • 27
  • 3
    *"I would like a synchronous method."* Any particular reason why? I don't think it's possible. – Felix Kling Jun 12 '13 at 14:54
  • firefox has/had the file.getAsDataURL() method, but it's deprecated and was the only sync version of any browser, afaik. – dandavis Jun 12 '13 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... – Laila Jun 12 '13 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 – Rup Jun 12 '13 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! – Laila Jun 13 '13 at 18:45

8 Answers8

45

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...
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
John Weisz
  • 30,137
  • 13
  • 89
  • 132
  • 1
    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. – ngr Jul 04 '19 at 21:59
  • 2
    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". – Mikhail V Apr 08 '20 at 23:14
  • 3
    Where is 'FileReaderSync' defined? I am getting error – sgowd May 26 '20 at 00:55
  • 2
    @sgowd Did you try in a web worker? It won't work on the main thread. – John Weisz May 26 '20 at 11:50
18

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.

sboisse
  • 4,860
  • 3
  • 37
  • 48
David Fariña
  • 1,536
  • 1
  • 18
  • 28
  • I used to have a similar trick, but it broke once I inserted the code in a loop. I finally used the callbacks methods combined with a closure to know when the loop was ending. – Laila Jul 17 '13 at 20:20
  • 1
    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. – David Fariña Jul 18 '13 at 09:45
  • 2
    https://stackoverflow.com/a/46832928/851951 this approach is good to, but it remains working asynchronously. – Dani Oct 19 '17 at 15:12
  • 1
    This of course is more up to date than my answer from 2013. – David Fariña Oct 24 '18 at 09:20
  • 6
    "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) – DavidJ Feb 21 '19 at 22:36
  • 3
    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. – David Fariña Apr 30 '19 at 14:36
  • 7
    This is NOT a synchronous task. – Jerry Feb 07 '20 at 10:50
4

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
HelpfulHelper
  • 226
  • 2
  • 5
  • 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 Nov 11 '22 at 11:58
  • @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))`. – HelpfulHelper Nov 12 '22 at 12:12
  • 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. – SScotti Nov 12 '22 at 13:14
  • Where do I get a file object from? All I have is a file name (URL) – Thomas Weller Dec 23 '22 at 18:47
  • @ThomasWeller you need to use XMLHttpRequest for that purpose. You only recieve a `File` object when a user selects a file into an `` html element. You can also reconstruct these functions to fetch your file. Just replace the `var url=URL.createObjectURL(file);` by `var url=file;` and remove the `URL.revokeObjectURL(url);`. then you can just pass your URL instead of the `File` object. I believe it is possible to construct a file object from some data fetched from an URL, but i don't know how to do this, if you choose this way, just google it. – HelpfulHelper Dec 23 '22 at 19:23
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();
  • 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. – Rotem Sep 27 '20 at 12:47
  • 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 – Bilal Nazer Sep 27 '20 at 13:22
  • 4
    You are using the await keyword and your code is therefore not synchronous. – sboisse Jul 08 '21 at 15:14
  • @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 ? – Nicolas David Jan 07 '22 at 15:54
  • @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. – sboisse Jan 07 '22 at 20:58
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'

randmin
  • 818
  • 8
  • 16
-3

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.

Jonathan Thurft
  • 4,087
  • 7
  • 47
  • 78
-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();
-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. ;)

Stephen Wylie
  • 924
  • 7
  • 10