Following is my piece of code which read file from the path specified
for (var i in e.target.files) {
var reader = new FileReader();
reader.onload = function (e) {
alert("File loaded successfully");
var output = e.target.result;
// console.log("output: "+output);
}
reader.log is asyncronous function what i want is to wait until reader.load event is fired then move to next iteration.
I also forcefully stop this by infinite for loop but my browser crashes on this. i have also tries settimeout and setinterval method but all in vain. i just to stop until reader.load event is fires and then move to next iteration.
JavaScript is built to be asynchronous. If low-level developer decided that some function need to be async, there is nothing you can do, and you should not, actually. Probably, it can take some time, and user will see his browser (or other runtime environment) hanged.
You should restructure the code, so you don't wait, but fire a callback for each asynchronous event, that would increment a counter, and do the function again. Something like:
var files = [],
fileCount = 0,
currentFile = 0;
for (var i in e.target.files) {
if (e.target.files.hasOwnProperty(i)) {
fileCount++;
}
}
function allLoaded() {
// process data.
}
function loadHandler(loadEvent) {
files.push(loadEvent.target);
loadNext();
}
(function loadNext() {
if (currentFile < fileCount) {
currentFile++;
var reader = new FileReader();
reader.onload = loadHandler;
} else {
allLoaded();
}
})();
Assuming your code snippet is missing a call similar to reader.log(i) the solution is as follows:
var currentItemIndex = 0;
if (e.target.files.length > currentItemIndex) {
readNext(e.target.files[currentItemIndex]);
}
else {
endOfWait();
}
function readNext(item) {
currentItemIndex ++;
var reader = new FileReader();
reader.onload = function (x) {
alert("File loaded successfully");
var output = x.target.result;
if (e.target.files.length > currentItemIndex) {
readNext(e.target.files[currentItemIndex]);
}
else {
endOfWait();
}
}
reader.log(item);
}
function endOfWait() {
// put code here that executes once the wait is over
}
Related
I have this code to read an Excel sheet into an array of objects. Everything seems to work fine except that readed.onload is performed only when I try to access the data that are not yet loaded (i.e., I suspect that reader.readAsArrayBuffer(file) did not have the time to fire the .onload event, am I right?). Then data are correctly loaded but the program stops at the end of loadFile(file) (i.e., after the resolve statement) seemingly because the call stack is empty.
To be complete, loadFile(file) is called by launchAll() which was called by a .onclick event.
I searched for similar but none reported such a program stop.
I cannot figure out what is going on!
function launchAll() {
var files = document.getElementById('file_upload').files;
if (files.length == 0) {
alert("Please choose any file...");
return;
}
loadFile(files[0]
createEmptyTree() // Creates forefather and foremother
createTree() // Creates DAGs using mappedData (a global variable)
}
async function loadFile(file) {
try {
let fileLoaded = await readFileAsync(file)
console.log("File loaded !!", fileLoaded)
} catch (err) {
console.log("Error during loading ", err)
}
};
function readFileAsync(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.onload = (event) => {
var data = event.target.result;
var workbook = XLSX.read(data, {
type: 'binary'
});
var roa = XLSX.utils.sheet_to_row_object_array(workbook.Sheets[workbook.SheetNames[0]]);
if (roa.length > 0) {
for (i = 0; i < roa.length; i++) {
mappedData.push(mapNode(roa[i], i))
}
}
resolve(event)
}
reader.onerror = (error) => {
reject(error)
};
reader.readAsArrayBuffer(file)
})
}
You need to await loadFile:
// Add async
async function launchAll() {
// ...
// Await loadFile
await loadFile(files[0])
createEmptyTree() // Creates forefather and foremother
createTree() // Creates DAGs using mappedData (a global variable)
}
I have a file containing JSON data that I want to read. I found some code for reading a file at neontribe.co.uk, but I can't seem to get the console to wait for the load to complete.
function onDeviceReady(){
window.resolveLocalFileSystemURL(cordova.file.externalDataDirectory, function(fs) {
var directoryReader = fs.createReader();
directoryReader.readEntries(function(entries) {
var i;
for (i=0; i<entries.length; i++) {
document.addEventListener('deviceready', onDeviceReady2, false);
function onDeviceReady2() {
function readFromFile(fileName) {
var pathToFile = cordova.file.externalDataDirectory + fileName;
window.resolveLocalFileSystemURL(pathToFile, function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader();
reader.onloadend = function (e) {
console.log("Inside Load" + JSON.parse(this.result));
return JSON.parse(this.result);
};
reader.readAsText(file);
}, errorHandler.bind(null, fileName));
}, errorHandler.bind(null, fileName));
}
function some_function(callback) {
console.log("inside function1");
var data = readFromFile(entries[i].name);
callback(data);
}
some_function(function(data) {
console.log("data!" + data);
console.log("Data String!" + JSON.stringify(data));
});
// var obj = JSON.parse(data);
// console.log("arresteeFirst!" + data.arresteeFirst);
// console.log("data!" + data);
}
console.log(entries[i].name);
}
}, function (error) {
alert(error.code);
});
}, function (error) {
alert(error.code);
});
}
When I run it I get this output in the console:
inside function1
Data!undefined
Data String!undefined
1452034357845.json
Inside Load[object Object]
So it looks like it goes to some_function and then prints the inside function 1. But then it does not wait for the function to pass the result from the load. It prints the two lines from the callback function straight away. It then prints the filename (at the end of the loop) and then finally prints the console message from within the load function. It looks like I am returning an object according to the console so data should not be undefined.
There are a few bits wrong with the code, mainly because it's not written taking into account the asynchronous calls and just assuming that everything happens in a synchronous fashion.
The callback(data) within some_function(callback) is in fact called after you called var data = readFromFile(entries[i].name);. But two issues arise here.
function readFromFile(fileName) doesn't return any data (actually, it doesn't return anything at all);
reader.readAsText(file); is treated asynchronous. In a nutshell, it means that your code will keep running (in your case print those messages) and call the reader.onloadend callback once the data is fully loaded - which would be too late for you as your message has already been printed.
There are several ways to fix this code, a good way is to use Promises. I like Bluebird for promises personally.
With promises, the solution would look something like this (pseudo-code):
function readFromFile(fileName) {
return new Promise(resolve, reject) {
var pathToFile = cordova.file.externalDataDirectory + fileName;
window.resolveLocalFileSystemURL(pathToFile, function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader();
reader.onloadend = function (e) {
console.log("Share the results... " + JSON.parse(this.result));
resolve(JSON.parse(this.result));
};
reader.readAsText(file);
}, errorHandler.bind(null, fileName));
}, errorHandler.bind(null, fileName));
} // Return the promise
}
function some_function(callback) {
console.log("inside function1");
var promise = readFromFile(entries[i].name);
promise.then(callback); // Will be called when promise is resolved
}
some_function(console.log);
Promises allows you to conceptually return a "contract" that the value will be fetched and all functions waiting for that value (.then) will be called once the data is received and the promise is resolved (you can reject a promise if it fails as well). (Sorry about the bad explanation, but there are loads of better documentation about it all over the internet, I recommend taking a look at it).
I hope it helps, there are other ways of coming around this problem, I believe that a promises might be the most elegant without getting into generators.
Given the following javascript code (or something equivalent):
var buf = [];
setInterval(function () {
buf.push("token");
// If buf has something pushed here we are screwed
if (buf.length == 1) {
sendCriticalLog();
}
});
setInterval(function () {
buf.push("other token");
});
Is there a way to ensure that the function of the first interval is atomic with regard to buf?
The only method I could come up with is:
function atomic(lock, cb){
var finish = function () {
lock.callbacks = lock.callbacks.slice(1);
if (lock.callbacks.length) {
lock.callbacks[0].call();
}
};
cb = cb.bind(null, finish);
if ((lock.callbacks = (lock.callbacks || []).concat([cb])).length == 1) {
// Nothing is running
lock.callbacks[0]();
};
}
var buf = [];
setInterval(function () {
atomic(buf, function () {
buf.push("token");
// If buf has something pushed here we are screwed
if (buf.length == 1) {
sendCriticalLog();
}
});
});
setInterval(function () {
atomic(buf, function () {
buf.push("other token");
});
});
But that is under the assumption that ((lock.callbacks = (lock.callbacks || []).concat([cb])).length == 1) will be guaranteed to be handled atomically. If, for example, concat is written in plain javascript this will probably not work...
JavaScript is not multithreaded, so your callbacks are in fact already "atomic". buf can only be altered between calls to the callback.
I'm trying to allow users to drag and drop a folder containing JavaScript files into an html5 page. This is what I currently have:
$scope.files = [];
//Establish dropzone
var dropbox;
dropbox = document.getElementById("fileDragAndDrop");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
//Events
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
};
function dragover(e) {
e.stopPropagation();
e.preventDefault();
};
function drop(e) {
e.stopPropagation();
e.preventDefault();
var items = e.dataTransfer.items;
for (var i = 0, item; item = items[i]; i ++) {
var entry = item.webkitGetAsEntry();
if(entry) {
traverseFileTree(entry);
}
}
};
//resursive file walker
function traverseFileTree(item) {
if(item.isFile) {
$scope.$apply(function () {
$scope.files.push(item);
});
} else if (item.isDirectory) {
var dirReader = item.createReader();
dirReader.readEntries(function(entries) {
for (var i = 0; i < entries.length; i++) {
traverseFileTree(entries[i]);
}
});
}
};
So the dragging and dropping works, but I'm having problems reading the file content.
$scope.parse = function () {
for(var i = 0; i < $scope.files.length; i++) {
var fileReader = new FileReader();
fileReader.onload = function (e) {
console.log(fileReader.result);
};
fileReader.onerror = function(err) {
console.log(err);
};
fileReader.readAsBinaryString($scope.files[i]);
}
};
I am not getting any error messages, which makes it hard to debug. Am I doing something wrong? has anyone had any issues doing similar tasks?
Not sure what your $scope is but giving it a go.
As you use webkitGetAsEntry() I assume this is for Chrome. From the looks of it your code should give you an error. If it does not, there is likely something you have omitted. You should typically get something like:
Uncaught TypeError: Failed to execute 'readAsBinaryString' on 'FileReader': The argument is not a Blob.
in your $scope.parse function.
There is a few issues. One is that you probably would read files as text and not as binary string. Secondly readAsBinaryString() is deprecated, use readAsArrayBuffer() if you want to read binary data.
Further, the webkitGetAsEntry() returns a FileEntry, hence why you should get the error mentioned above. To read the file you could typically do:
$scope.files[i].file(success_call_back, [error_call_back]);
For example:
function my_parser(file) {
var fileReader = new FileReader();
fileReader.onload = function (e) {
console.log(fileReader.result);
};
fileReader.onerror = function(err) {
console.log(err);
};
console.log('Read', file);
// Here you could (should) switch to another read operation
// such as text or binary array
fileReader.readAsBinaryString(file);
}
$scope.files[0].file(my_parser);
This will give you a File object as argument to my_parser(). Then you could typically check .type and use appropriate read function. (Though be aware of the slackness in MIME type. As in: do not rely on it, but use it as a hint.)
if (file.type.match(/application\/javascript|text\/.*/)) {
// Use text read
} else if (file.type.match(/^image\/.*/)) {
// Use binary read, read as dataURL etc.
} else ...
$scope.parse = function () {
for(var i = 0; i < $scope.files.length; i++) {
var fileReader = new FileReader();
fileReader.onload = function (e) {
console.log(fileReader.result);
};
fileReader.onerror = function(err) {
console.log(err);
};
fileReader.readAsText($scope.files[i]);
}
};
Below is working on mine, this is typescript
You should convert FileEntry To Standard File, before passing to FileReader.
const convertFileEntryToStandardFile = async (files: any[]) => {
if (files) {
let plainFiles = [];
files.forEach(f=> {
let plainFile = readFile(f);
plainFiles.push(plainFile);
});
return plainFiles;
}
}
const readFile = async (fileEntry: any) => {
return await new Promise((resolve, reject) => fileEntry.file(resolve, reject));
}
This might be a little hard to follow.
I've got a function inside an object:
f_openFRHandler: function(input) {
console.debug('f_openFRHandler');
try{
//throw 'foo';
DragDrop.FileChanged(input);
//foxyface.window.close();
}
catch(e){
console.error(e);
jQuery('#foxyface_open_errors').append('<div>Max local storage limit reached, unable to store new images in your browser. Please remove some images and try again.</div>');
}
},
inside the try block it calls:
this.FileChanged = function(input) {
// FileUploadManager.addFileInput(input);
console.debug(input);
var files = input.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!file.type.match(/image.*/)) continue;
var reader = new FileReader();
reader.onload = (function(f, isLast) {
return function(e) {
if (files.length == 1) {
LocalStorageManager.addImage(f.name, e.target.result, false, true);
LocalStorageManager.loadCurrentImage();
//foxyface.window.close();
}
else {
FileUploadManager.addFileData(f, e.target.result); // add multiple files to list
if (isLast) setTimeout(function() { LocalStorageManager.loadCurrentImage() },100);
}
};
})(file, i == files.length - 1);
reader.readAsDataURL(file);
}
return true;
LocalStorageManager.addImage calls:
this.setItem = function(data){
localStorage.setItem('ImageStore', $.json_encode(data));
}
localStorage.setItem throws an error if too much local storage has been used. I want to catch that error in f_openFRHandler (first code sample), but it's being sent to the error console instead of the catch block. I tried the following code in my Firebug console to make sure I'm not crazy and it works as expected despite many levels of function nesting:
try{
(function(){
(function(){
throw 'foo'
})()
})()
}
catch(e){
console.debug(e)
}
Any ideas?
var reader = new FileReader();
reader.onload = (function(f, isLast) {
Likely that's your problem right there - the FileReader probably calls onload asynchronously, at a time when your try/catch is no longer in scope. There may be a separate error handler function available on the FileReader interface, or you might need to move the try/catch into the anonymous function you're passing to onread().
I think the problem is that the error is happening later, after your f_openFRHandler function has completed. Note that the function where LocalStorageManager.addImage is being called is being set as the onload handler on the reader, not being called immediately. It gets called later, asynchronously, when the data is loaded.
You'll need to put your try..catch inside the anonymous function being created and assigned to that event, e.g.:
this.FileChanged = function(input) {
// FileUploadManager.addFileInput(input);
console.debug(input);
var files = input.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!file.type.match(/image.*/)) continue;
var reader = new FileReader();
reader.onload = (function(f, isLast) {
return function(e) {
try { // ADDED
if (files.length == 1) {
LocalStorageManager.addImage(f.name, e.target.result, false, true);
LocalStorageManager.loadCurrentImage();
//foxyface.window.close();
}
else {
FileUploadManager.addFileData(f, e.target.result); // add multiple files to list
if (isLast) setTimeout(function() { LocalStorageManager.loadCurrentImage() },100);
}
}
catch (err) { // ADDED
// YOUR HANDLING HERE
}
};
})(file, i == files.length - 1);
reader.readAsDataURL(file);
}
return true;
};
Your (excellent) test case makes the call synchronously, which is why the error is caught when you try it. This is a closer model to what's actually happening:
try{
(function(){
setTimeout(function(){ // Use setTimeout to model asynchronicity
throw 'foo'
}, 100);
})()
}
catch(e){
console.debug(e)
}