Angular2 : Asynchronous upload array of files - javascript

I'm trying to upload an array of files using FileReader which are base64 encoded and stored in an array for further processing. I'm struggling to understand the pattern I need to create in order to ensure all files have been uploaded because I have to wait for the onload event handler to fire. For example;
If I pass an array of files to the following function it will resolve the promise before the files are actually uploaded.
localUploads( event ) : any {
var response = [];
return new Promise( function(resolve, reject) {
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = (function(f) {
return function(e) {
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
};
})(f);
}
resolve( response );
}
r.readAsText(f);
});
}
Can anybody please advise a novice?
Many thanks.

It's all about resolving the promise at the right time. Currently you resolve it when the loop is done, but that doesn't mean that all the items have been processed. I haven't used FileReader, but you should be able to do something like this:
localUploads( event ) : any {
var response = [];
return new Promise(function(resolve, reject) {
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = function(e) { // Possible clean-up?
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
resolve( response );
}
};
// Moved here to be able to access r and f variables
r.readAsText(f);
}
}
});
}
Or the old way using $q.
var response = [];
var dfd = $q.defer();
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = function(e) { // Possible clean-up?
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
dfd.resolve(response);
}
};
// Moved here to be able to access r and f variables
r.readAsText(f);
}
}
else {
// Possible resolve promise here to?
}
return dfd.promise;
Note that you might also want to handle the possible errors. If one of the files isn't completed sucessfully the promise will never be resolved.
You might need to resolve the promise in onloadend instead of onload. I really cannot figure it out from the docs.
var r = new FileReader();
r.onload = function(e) {
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
dfd.resolve(response);
}
}

Related

Read data url in for each promise?

I want to replace in my object a firebase child data image url by a database64 string. It work pretty well but i have issue with async ? how can i wait for all my value inside my foreach ?
return FirebaseRef.child('rav').on('value', snapshot => {
// const userData = snapshot.val() || [];
var obj = {
}
snapshot.forEach(child => {
function toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
toDataURL(child.val().avatar, function (dataUrl) {
// console.log('RESULT:', dataUrl)
obj[child.key] = {
...child.val(),
avatar: dataUrl
}
console.log('obj', obj)
})
});
console.log('obj', obj)
this.updateAvatar({ profile: obj }); // Send to reducer
});
},
}),
Create an empty array before the forEach, have the callback() function return a Promise.resolve(), push the return value (the promise) of callback() into your array, then (after the forEach) use Promise.resolve() with your array to trigger the action you want to call after all the FileReader() calls have completed.
Let me start by stating that you shouldn't define a function inside a loop. It's redefining at every step of the loop. That's not your issue though. Lucky for you I wrote such a function earlier this year. This dataURL was found on wikipedia.
function dataURLtoBlob(dataURL){
var d;
if(typeof dataURL !== 'string' || !((d = dataURL.split(';base64,')) && d.length > 1)){
return false;
}
var s = atob(decodeURIComponent(d[1])), l = s.length, a = new Uint8Array(l);
for(var i=0; i<l; i++){
a[i] = s.charCodeAt(i);
}
return new Blob([a], {type:'image/png'});
}
var url = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kfr6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////////////////////////////////////////////////////////////////wAARCAAYAEADAREAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIRAAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYyJLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=';
console.log(url);
dataURLtoBlob(url).text().then(function(str){
console.log(str);
});
<img src='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kfr6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////////////////////////////////////////////////////////////////wAARCAAYAEADAREAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIRAAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYyJLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k='/>
Of course, you should ask yourself why you're doing this when you can store an image in Firebase.
PS
You should be able to send a Blob to your Server using FormDataInstance.append.

Handling multiple file upload: When you push something to an array, when does the array update?

I'm trying to make a file input that can handle multiple CSV files being uploaded at the same time. I loop through each file, run it through some data cleaning functions and then put it into a global array. My problem is that the array doesn't appear to update despite the fact that it appears updated when I console.log it.
Here is a recreation of my problem.
My HTML:
<input type="file" id="myInput" multiple>
And my code:
GLOBALARR = [];
$('#myInput').on('change',function(e) {
files = e.target.files;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
}
}
checkArray();
});
function pushFileContentsToArray(csv) {
GLOBALARR.push(csv);
}
function checkArray() {
console.log(GLOBALARR);
console.log(GLOBALARR.length);
}
Notice that the console.log(GLOBALARR) outputs the updated array, but the console.log(GLOBALARR.length) outputs 0 as the length. When I try to work with the elements in the array, I get undefined errors and whatnot, as if the array is still empty.
Can someone help me understand what is going on?
onload is an async operation, so you're calling checkArray() before the file has been read. To fix this, move the checkArray() call to just after the pushFileContentsToArray() call:
$('#myInput').on('change', function(e) {
files = e.target.files;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
checkArray();
}
}
});
Obviously this is going to perform this logic for every file you read. If you want to only call checkArray() once all files have been read you could create your own Promise and resolve it after onload has fired for all files, something like this:
$('#myInput').on('change', function(e) {
let files = e.target.files;
let filesRead = 0;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
if (++filesRead === files.length);
checkArray();
}
}
});

Can't post the javascript array of object

I am trying to post javascript array of object in an ajax call but i get string value "[]". When i try to console.log the array lenght it says zero.
Following is the code i am using
var masterFileArray = []; // where I will store the contents
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
};
})(f);
r.readAsText(f);
}
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
Thats how it looks like on inspection
What i am doing wrong? Any help would be appreciated, Thank you.
You can post after reading finished,
Here introduced left_loaded_count to get the status of reading.
Try like this.
var masterFileArray = []; // where I will store the contents
var left_loaded_count = 0;
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
left_loaded_count -= 1;
if(left_loaded_count == 0)
{
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
}
};
})(f);
left_loaded_count += 1;
r.readAsText(f);
}
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
readAsText() is an asynchronous operation, but you proceed with the AJAX call right away instead of waiting for the read operations to finish. That's why your console.log(masterFileArray) prints an empty array, when it runs none of the operations have finished and the array is still empty.
The best way to solve this is to wrap each file read operation in a promise and then proceed with the AJAX call once all these promises resolve.
Get rid of var masterFileArray = [] and change your code within the if (files) { ... } block to this:
Promise.all(files.map(function(f) {
return new Promise(function(resolve) {
var r = new FileReader();
r.onload = function (e) {
var contents = e.target.result;
resolve({name:f.name, contents: contents, type:f.type, size:f.size}); // resolve promise with object
};
r.readAsText(f);
});
})).then(function(masterFileArray) {
// All promises have resolved and their results have been collected in masterFileArray
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
);
});

Wait until all files are read asynchronously (FileReader) and then run code

I have a page where the user can select a folder to upload files. Before sending the files, I need to read them and check the data. My code is organized as follows:
$( '#folder-select' ).on('change', getValidFileList);
var fileList = [];
var getValidFileList = function(event) {
//Get the selected files
files = $( this ).get(0).files;
for(var i=0; i<files.length; i++) {
checkFile(files[i]);
}
//Do something with the final fileList
console.log(fileList);
};
var checkFile = function(file) {
var reader = new FileReader();
reader.onload = function (event) {
//Here I parse and check the data and if valid append it to fileList
};
reader.readAsArrayBuffer(file);
};
I would like to take the resulting fileList array to keep processing/displaying the uploaded files. I found that reader.onload() is called asynchronously, so the result of the console.log(fileList) after the for loop is an empty array (it is executed before the reader.onload() is fired). Is there any way to wait until all files are read and appended to fileList?
Just keep track of how many files has been processed compared to how many files has been given:
function getValidFileList(files, callback) {
var count = files.length; // total number of files
var fileList = []; // accepted files
//Get the selected files
for(var i = 0; i < count; i++) { // invoke readers
checkFile(files[i]);
}
function checkFile(file) {
var reader = new FileReader();
reader.onload = function(event) {
var arrayBuffer = this.result;
//Here I parse and check the data and if valid append it to fileList
fileList.push(arrayBuffer); // or the original `file` blob..
if (!--count) callback(fileList); // when done, invoke callback
};
reader.readAsArrayBuffer(file);
}
};
The --count will subtract one per reader onload hit. When =0 (or !count) it invokes the callback. Notice that the array order may not be the same as the one from files[n] it this should matter.
Then invoke it like this:
$( '#folder-select' ).on('change', function() {
getValidFileList(this.files, onDone)
});
function onDone(fileList) {
// continue from here
}

JavaScript Async readAsDataURL multiple files

I have a list of files I need to save and in addition to the name I need to send the readAsDataURL to the server as well.
The problem is I am not sure how to do it with the async nature of readAsDataURL. Because to save the DATAURL to the array I need to look up the name of the file which is in the files list. and I cannot pass the file to the async method of readAsDataURL. How do you write this properly to work? The end result is I want a list of files sent to the server in one JSZip file.
function saveFileList(files)
{
for (var i = 0, file; file = files[i]; i++) {
var fr = new FileReader();
fr.onload = function(e){
if (e.target.readyState == FileReader.DONE) {
var tt = e.target.result.split(",")[1];
//update the record in the list with the result
}
};
var pp = fr.readAsDataURL(file);
}
If you have FileList and you need to get array of base64 string, you need do this
export async function fileListToBase64(fileList) {
// create function which return resolved promise
// with data:base64 string
function getBase64(file) {
const reader = new FileReader()
return new Promise(resolve => {
reader.onload = ev => {
resolve(ev.target.result)
}
reader.readAsDataURL(file)
})
}
// here will be array of promisified functions
const promises = []
// loop through fileList with for loop
for (let i = 0; i < fileList.length; i++) {
promises.push(getBase64(fileList[i]))
}
// array with base64 strings
return await Promise.all(promises)
}
use it like this
const arrayOfBase64 = await fileListToBase64(yourFileList)
You need another function around it, so you can pass the file in. Try this:
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
if(reader.readyState == FileReader.DONE)
alert(theFile.name); // The file that was passed in.
}
};
})(file);
reader.readAsDataURL(file);
An alternative to Russell G's answer:
var reader = new FileReader();
reader.onload = function(event){
payload = event.target.result;
var filename = file.name, filetype = file.type;//etc
//trigger a custom event or execute a callback here to send your data to server.
};
reader.onerror = function(event){
//handle any error in here.
};
reader.readAsDataURL(file);

Categories

Resources