Search code examples
javascripthtmlcryptojs

Wait for FileReader slice blob entirely


I'm trying to read files data with FileReader API, and I read so many questions with this same problem and tried to implement their solutions, but for some bug in my code (maybe), it keep doing wrong result. To avoid crashing the browser when the file is too large (more than 15MB) I decided to start using the 'slice' method. But the problem is that as the FileReader onload event is async, I get at the part of the code where it should have the completed buffer ready to soon, when it's still incomplete. And I can't make everything inside the onload event, because it have only part of the total buffer. Here is my code:

function readBlob(opt_startByte, opt_stopByte) 
{
    var file = window.files[0];
    var start = parseInt(opt_startByte) || 0;
    var stop = parseInt(opt_stopByte) || file.size - 1;

    var reader = new FileReader();

    // If we use onloadend, we need to check the readyState.
    reader.onload = function(evt) {           
        window.bits.push(CryptoJS.AES.encrypt(evt.target.result, window.password)); 
    };
    var blob = file.slice(start, stop + 1);
    reader.readAsDataURL(blob);
}

function encrypt()
{
    window.files = document.getElementById('encrypt-input').files;
    if (!window.files.length) {
        alert('Please select a file!');
        return;
    }
    window.password = $('#key').val();
    window.bits = [];
    var startByte = 0;              
    var endByte = 0;
    while(startByte <= document.querySelector('input[type=file]').files[0].size - 1)
    {       
        endByte = startByte + 1024;
        readBlob(startByte, endByte);
        startByte = endByte;
        if (startByte >= document.querySelector('input[type=file]').files[0].size)
        {
            alert('Here I want the total array length:' + window.bits.length);      
        }   
    }   
            alert('Here I want the total array length:' + window.bits.length);          
}

As you can see right above, I tried this:

    if (startByte >= document.querySelector('input[type=file]').files[0].size)
    {
        alert('Here I want the total array length:' + window.bits.length);      
    }   

But I still getting the length 0 for some reason. How can I process the entire buffer when using the slice method? Another secondary question is: inside the onload event, I call a synchronous function 'CryptoJS.AES.encrypt', but as this event is async, I was expecting that the browser won't freeze while processing the buffer into this event. Is there any way that I can implement something to avoid freezing the browser while processing the file?


Solution

  • The reason this happens is that reader.readAsDataURL() is asynchronous. You have to pass a callback to readBlob which is called when it is finished:

    function readBlob(opt_startByte, opt_stopByte, done) {
        ...
        reader.onload = function(evt) {           
            window.bits.push(CryptoJS.AES.encrypt(evt.target.result, window.password));
            done();
        };
        ...
    }
    
    function encrypt() {    
        ...
        var startByte = 0;              
        var endByte = 0;
        var callback = function(){
            alert('Here I want the total array length:' + window.bits.length);
        };
        while(startByte <= document.querySelector('input[type=file]').files[0].size - 1) {
            endByte = startByte + 1024;
            (function(startByte, endByte, newCallback){
                callback = function(){
                    readBlob(startByte, endByte, newCallback);
                };
            })(startByte, endByte, callback);
            startByte = endByte;
        }
        callback(); // run the chain
    }
    

    There is simply no other sensible way, because JavaScript doesn't have a sleep function.