FileReader JS Api Call Fire before it's ready - javascript

I'm trying to read a file, create a "FileContainer", and a DataUrl from a fileReader so i can send it to a web api.
My problem is that the Api call fires before my object is created, so i send Null to the api. which means that if i send a small file, it works.
My code for the reader look something like this
var reader = new FileReader();
reader.onload = (function (theFile) {
return function (e) {
var newFile = {
name: theFile.name,
type: theFile.type,
size: theFile.size,
lastModifiedDate: theFile.lastModifiedDate
}
var binaryString = e.target.result;
updloadedFile(newFile, binaryString, selectedFolder, scope, fileProgrss);
}
};
})(f);
reader.readAsDataURL(f)
And for my http.post call
function updloadedFile(file, data, selectedFolder, scope, fileProgrss) {
var dummyobj = {
Name: file.name,
Extension: file.name.split('.')[0],
Path: selectedFolder.Path,
DataString: data,
}
$http.post('/api/Files/Upload/',
JSON.stringify(dummyobj),
{
headers: {
'Content-Type': 'application/json'
}
}
).success(function (data2) {
}).error(function (data, status, headers, config) {
});
}

You need to wrap your FileReader in a promise
function fileReader(file) { // perform async operation
var deferred = $q.defer();
var reader = new FileReader();
reader.onload = function() {
// Your reader.onload code here
deferred.resolve(reader.result);
};
reader.readAsDataURL(f);
return deferred.promise;
};
You can then call the uploadedFile() function like this:
fileReader().then(function(result){
updloadedFile(..., ..., ..., ...)
});
By using the promise object, you can access the result of of the async task when it completes by using then. then runs as soon the result is available.
If you want to read more about promises, this SO thread does a great job explaining it.

I am guessing that the issue here is, you are listening to onload, apparantly it gets called on each read operation, thus might be null in beginning( reference), try changing it to onloadend.

I don't really see the need for wrapping the FileReader API call within a promise. You could do that but according to the docs (https://developer.mozilla.org/en-US/docs/Web/API/FileReader) FileReader.onload gets called when the data is ready. Doing a additional deffered.resolved to be able to use then is just a nice to have.
I have created a fiddle. Even with big files the http call is made only if the data is available. I don't really see an error in your code. May you can try it out with the fiddle.
http://jsfiddle.net/tjruzpmb/212/

Related

FileReader onload and assign value in loop?

i'm having difficulties on getting file reader content assignment. Is there anyway to wait the file reader finish onload and assign the file content before it push to the array?
I have a list of input file type button as below:
#for (int i = 0; i < #Model.LetterList.Count(); i++)
{
<tr>
<td>
<input type="file" id="LetterAttachment" name="LetterAttachment" accept="application/pdf">
</td>
</tr>
}
When i click submit, i want to assign the file content value into my form list in loop, below is my javascript code :
var attach=""; // empty variable for assign file content
function ApproverAction(action) {
var formList = [];
$("input[name='LetterAttachment']").each(function () {
if (this.files && this.files[0]) {
// I perform file reader here to assign the file content into attach....
var FR = new FileReader();
FR.onload = function (e) {
attach = e.target.result;
}
FR.readAsDataURL(this.files[0]);
var form = {
ID: newGuid(),
FileContents: attach, <<< ---- However it showing empty
DocumentName: this.files[0].name,
DocumentSize: this.files[0].size,
DocumentContentType: 'application/pdf',
SourceType: 'OnlineAssessment',
CreatedDate: '#DateTime.Now'
}
formList.push(form);
}
});
console.log(formList);
}
However i can't get the result quite correctly for output :
Any help and tips is highly appreciated! Thanks!
Use a promise for each file that resolves in the onload function and push those promises into an array
Then use Promise.all() to send the data once all promises have resolved. Note that the error handling will need to be improved depending on process flow you want
function ApproverAction(action) {
var filePromises = [];
$("input[name='LetterAttachment']").each(function() {
if (this.files && this.files[0]) {
// reference to this to use inside onload function
var _input = this;
var promise = new Promise(function(resolve, reject) {
var FR = new FileReader();
FR.onload = function(e) {
var form = {
ID: newGuid(),
FileContents: e.target.result;,
DocumentName: _input.files[0].name,
DocumentSize: _input.files[0].size,
DocumentContentType: 'application/pdf',
SourceType: 'OnlineAssessment',
CreatedDate: '#DateTime.Now'
}
// resolve promise with object
resolve(form);
}
FR.readAsDataURL(this.files[0]);
if(FB.error){
// needs more robust error handling, for now just reject promise
reject(FB.error)
}
// push promise to array
filePromises.push(promise)
}
});
}
});
// return a new promise with all the data
return Promise.all(filePromises)
}
Usage with promise returned from function
ApproverAction(action).then(function(formList){
// do something with the data array
console.log(formList);
}).catch(function(err){
console.error("Ooops something went wrong')
});
This is because the function you provided within the FR.onload is executed asynchronously. So, the code after it will be executed before the function is called thus the value of FileContents in the JSON is empty.
What you can do is either do all the stuff you want to do within the function or use some other function like readAsText.
...
var FR = new FileReader();
FR.readAsDataURL(this.files[0]);
var form = {
ID: newGuid(),
FileContents: FR.readAsText(this.files[0),
DocumentName: this.files[0].name,
DocumentSize: this.files[0].size,
DocumentContentType: 'application/pdf',
SourceType: 'OnlineAssessment',
CreatedDate: '#DateTime.Now'
}
...
Refer to this example for onLoad and readAsText.

Javascript Downloading and reading file contents from Dropbox

I am trying to download a file I uploaded as test to Dropbox. The download function works and I am getting the fileblob as well but having trouble to actually read the file contents
function downloadFile() {
dbx.filesDownload({path: '/_bk_test/test3.json'})
.then(function(response) {
var blob = response.fileBlob;
var reader = new FileReader();
reader.addEventListener("loadend", function() {
console.log(reader.result); // will print out file content
});
reader.readAsText(blob);
})
.catch(function(error) {
console.error(error);
});
}
But I am getting this error as output
Promise {<pending>}
VM215:11 TypeError: reader.addEventListener is not a function
at <anonymous>:5:24
This is strange.
But if I store the response.fileBlob in a global variable and then use the reader function, it wont show the TypeError. But I still cant read the file contents.
Either way, these are the issues
1. In a function the FileReader is throwing an exception.
2. Outside the function, the FileReader is not showing the file contents.
PS - Testing in Cordova
Alright, Cordova has a different API
function downloadFile() {
dbx.filesDownload({path: '/_bk_test/test3.json'})
.then(function(response) {
var blob = response.fileBlob;
var reader = new FileReader();
reader.onloadend = function(evt) {
console.log("read success");
console.log(evt.target.result);
};
reader.readAsText(blob);
})
.catch(function(error) {
console.error(error);
});
}

JavaScript FileReader API asynchronous - how to wait?

i have implemented an Filereader with the FileReader API of JavaScript.
My problem is that I have to use it´s result, but since it´s asynchronous I can´t figure out how to get the data when it´s needed.
simplified:
function MyReader() {
reader = new FileReader();
this.myResult=[];
//callback;
parse= function (evt) {
switch (evt.target.readyState) {
case FileReader.DONE:
var result = evt.target.result;
//parsing the file and save into myResult
callback(myResult);
break;
case FileReader.LOADING:
break;
case FileReader.EMPTY:
break;
}
}}
MyReader.prototype = {
read: function (callback) {
//callback=callback;
var files = document.getElementById('files').files;
var file = files[0];
reader.onloadend = parse;
var blob = file.slice(0, file.size);
reader.readAsArrayBuffer(blob);
},
getResult: function () {
alert(myResult.length);
}};
//index.html
<script>reader = new MyReader();
reader.read();
//reader.read(function(res){alert("call: "+ res)});
reader.getResult();
alert("bla"); </script>
When I now run that I get first "bla" and then the length of the result.
But I need the result directly after I call read().
How can I ensure that the Reader is done when I need it? I already tried to introduce an "state" Variable and check that, but it didn´t worked.
The only other idea I had was to use an callback with the function, which needs the result and add that to parse, but when I added a callback it doesn´t work and I don´t want to use an callback. Is it not possible to use some kind of mutexes? (Although it would be shit if the browser blocks....)

Access variable outside foreach Jquery

I'm trying to access my variable but it is giving me an empty array.
example:
var data = [];
$.each(files, function (index, file) {
var reader = new FileReader();
reader.onload = handleReaderLoad;
reader.readAsDataURL(file);
function handleReaderLoad(evt) {
data.push({
name: file.name,
file: evt.target.result
});
}
});
console.log(data)
At this moment data is an empty array. When i put the console.log in the foreach it is returning my data. How can i return and acces the data that is filled outside of the foreach?
handleReaderLoad will be called in an asynchrounous fashion. You can keep track of the number of files and log in the onload when the file count is complete
var data = [];
var fileCount = files.length;
var currentCount = 0;
$.each(files, function(index, file) {
var reader = new FileReader();
reader.onload = handleReaderLoad;
reader.readAsDataURL(file);
function handleReaderLoad(evt) {
data.push({
name: file.name,
file: evt.target.result
});
currentCount++;
if (currentCount == fileCount) {
console.log(data);
}
}
});
I'm not familiar with FileReader but it's obvious that method readAsDataURLis called asynchronous.
Your onload method is not called inmediatly, your code flow continues without waiting for that to trigger so at the point console.log(data) is reached, data is not filled yet.
JavaScript is a single-threaded language. This means that invoking a long-running process blocks all execution until that process completes. UI elements are unresponsive, animations pause, and no other code in the app can run. The solution to this problem is to avoid synchronous execution as much as possible.
One way to do this is to have a function execute at a later time, as with event handlers, which are invoked after another call has raised an event. Callback functions are another kind of asynchronous processing, because they call back into the code that initiated the process.
Asynchronous programming in JavaScript
This could do the trick.
var data = [];
$.each(files, function (index, file) {
var reader = new FileReader();
reader.onloadend = handleReaderLoad;
reader.readAsDataURL(file);
});
function handleReaderLoad(evt) {
data.push({
name: file.name,
file: evt.target.result
});
if(files.length == data.length)
console.log(data);
}

jQuery-File-Upload where is the file data when pre processing? http://blueimp.github.io/jQuery-File-Upload/

I've started playing around with the excellent http://blueimp.github.io/jQuery-File-Upload/ file upload project.
From the File Processing Options section of the documentation it seems that jquery.fileupload-process.js will let me parse and even modify the file's binary data (files array - with the result of the process applied and originalFiles with the original uploaded files)
(to parse, or append to it or encrypt it or to do something to it)
but for the life of me I can't seem to figure out where is the actual file data within the array so that I can pre-process it before it uploads.
What part of the data array has the "something.pdf" binary file in it? so that I can parse and transform it before upload?
//FROM: jquery.fileupload-process.js
//The list of processing actions:
processQueue: [
{
action: 'log'
}
],
add: function (e, data) {
var $this = $(this);
data.process(function () {
return $this.fileupload('process', data);
});
originalAdd.call(this, e, data);
}
},
processActions: {
log: function (data, options) {
console.log(data.files[0]); //Is it here?
console.log(data); //Is it here?
console.log(data.files[data.index]); //Is it here?
console.log(data.files[data.index].name); //Is it here?
//where?
Thank you.
The correct way to access the currently processed file is the following:
var file = data.files[data.index];
For browsers which support the File API, this is a File object.
To retrieve the actual File data, we have to use the FileReader interface:
var fileReader = new FileReader();
fileReader.onload = function (event) {
var buffer = event.target.result;
// TODO: Do something with the ArrayBuffer containing the file's data
};
fileReader.readAsArrayBuffer(file);
It may be useful, building on #Sebastian's answer, to explain where to put the FileReader, to work with the fileupload plugin. (I would have made this a comment on #Sebastian's answer, but didn't have space in the comment field).
I have built this in to the process action (using readAsText instead of readAsArrayBuffer) as follows:
alertText: function(data, options) {
var dfd = $.Deferred(),
file = data.files[data.index];
var fileReader = new FileReader();
fileReader.onload = function (event) {
var fileText = event.target.result;
alert('Text of uploaded file: '+ fileText);
};
fileReader.readAsText(file);
return dfd.promise();
}

Categories