Laila
Laila

Reputation: 1511

Read a file synchronously in JavaScript

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?

Upvotes: 71

Views: 91606

Answers (8)

randmin
randmin

Reputation: 948

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'

Upvotes: 0

HelpfulHelper
HelpfulHelper

Reputation: 294

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

Upvotes: 6

John Weisz
John Weisz

Reputation: 32012

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...
});

Upvotes: 50

David Fari&#241;a
David Fari&#241;a

Reputation: 1604

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.

Upvotes: 18

Bilal Nazer
Bilal Nazer

Reputation: 27

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();

Upvotes: -3

Bilal Nazer
Bilal Nazer

Reputation: 27

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();

Upvotes: 0

Jonathan Thurft
Jonathan Thurft

Reputation: 4173

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.

Upvotes: -2

Stephen Wylie
Stephen Wylie

Reputation: 934

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. ;)

Upvotes: -4

Related Questions