Get variable from a asynchronous function - javascript

I'm using the following javascript solution to get the number of pages of a file :
const reader = new FileReader()
reader.readAsBinaryString(file)
reader.onloadend = function () {
const count = reader.result.match(/\/Type[\s]*\/Page[^s]/g).length
console.log('Number of Pages:', count)
}
The number of pages is correct in the console but I don't know how to extract that number from the scope of the reader so I can use it elsewhere.
I've read How to return the response from an asynchronous call but I don't understand how to implement it for my case

Wrap it in a promise and resolve the value you want:
function getPageNumber() {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsBinaryString(file)
reader.onloadend = function () {
const count = reader.result.match(/\/Type[\s]*\/Page[^s]/g).length
console.log('Number of Pages:', count);
resolve(count);
}
}
}
getPageNumber().then(count => {
// here, now you have count
});

Related

Async/await with FileReader issue

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

how to get the value from a Promise resolve?

I am trying to read user uploaded file and convert it into String. I have 2 functions to do this.
handleFileInput
handleFileInput(event){
setTimeOut(async()=>{
let abcd= await this.convertFileToString(this.file) //the file has been uloaded successFully at this point
console.log(abcd) //this prints the enitre fn given in the resolve method
},3000)
}
convertFileToString
convertFileToString(file){
return new Promise((resolve, reject)=>{
let fileReader = new FileReader();
fileReader.readAsText(file);
resolve(fileReader.onload = (event) =>{
this.XMLAsString=fileReader.result as String
})
})
}
When i print the value of abcd in the console i get this:
ƒ (event) {
_this.XMLAsString = fileReader.result;
}
I am fairly new to the concept of async/await and Promises and understand that promise is the only asynchronous thing i can await. I want the value of the uploaded file (converted to String) to be stored in variable abcd. how do i get the value? Or if i have to return a promise, then how do i access the value of file read as String and store in in abcd?
Your convertFileToString looks a little wrong: you should be invoking resolve() in the onload handler, not the other way round:
convertFileToString(file){
return new Promise((resolve, reject)=>{
let fileReader = new FileReader();
fileReader.readAsText(file);
fileReader.onload = (event) => {
resolve(event.target.result);
}
})
}

How to use async/await with FileReader in for-loop function in google-apps-script web app?

I have created a Google Apps Script web application for uploading images to my Google Drive. It has a lot of rows, each row has an input tag for uploading file. I created an only one submit button to upload all chosen images in one time. However, I would like each row to upload each image in order and then delete that row when it was uploaded successfully in order as well. The problem is I can't find the right way to use async/await function to upload the images to Drive with FileReader because when I run the script, It's still work as asynchronous function.
async function uploadImage() {
var row = document.getElementsByClassName('row');
var file = document.getElementsByClassName('img-file');
var name = document.getElementsByClassName('img-name');
for (let i=0; i<row.length; i++) {
var image = file[i].files[0];
if (image) {
var reader = new FileReader();
reader.readAsDataURL(image);
reader.onloadend = async (event) => {
await new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(r => resolve())
.uploadImgToDrive(name[i].value, event.target.result)
}).then(() => row[i].innerHTML='');
}
}
}
}
If I understood what your goal is, the following code should work. Only one image at a time will be uploaded. Let me know if it fit your needs.
Please note that your function will always be asynchronous though because you have two asynchronous tasks inside it (FileReader and API call). The only thing you can decide is how many operations you want to handle "at the same time".
Finally, remember that anytime you use an async function it will immediately return an unresolved promise that will resolve with the value that the function returns when it finishes running.
Inside async functions, await is used to "wait" for a promise to resolve before continuing (in this case, the promise that you are creating with new Promise()), so it is similar to using .then() directly on the promise (you don't need both, that is why I removed the .then() part).
function uploadImages() {
var row = document.getElementsByClassName('row');
var file = document.getElementsByClassName('img-file');
var name = document.getElementsByClassName('img-name');
(function nextImg(i) {
var image = file[i].files[0];
if (image) {
var reader = new FileReader();
reader.readAsDataURL(image);
reader.onloadend = async (event) => {
await new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(r => resolve())
.uploadImgToDrive(name[i].value, event.target.result);
});
row[i].innerHTML='';
if (i < row.length - 1) {
nextImg(i + 1);
}
};
}
})(0);
}
Optimised version (not tested):
Avoids using innerHTML (important) and tries to reuse FileReader() instance (not sure if it will work).
function uploadImages() {
let row = document.getElementsByClassName('row');
let file = document.getElementsByClassName('img-file');
let name = document.getElementsByClassName('img-name');
let reader = new FileReader();
(function nextImg(i) {
if (file[i].files[0]) {
reader.onloadend = async function onloadend(e) {
await new Promise((resolve) => {
google.script.run.withSuccessHandler(r => resolve(r)).uploadImgToDrive(name[i].value, e.target.result);
});
while (row[i].firstChild) {
row[i].removeChild(row[i].firstChild);
}
if (i < row.length - 1) {
nextImg(i + 1);
}
};
reader.readAsDataURL(file[i].files[0]);
}
})(0);
}
Another way to do this would be to hook up the loadend event of reader to a new promise and chain it:
async function uploadImage() {
var row = document.getElementsByClassName('row');
var file = document.getElementsByClassName('img-file');
var name = document.getElementsByClassName('img-name');
for (let i=0; i<row.length; i++) {
var image = file[i].files[0];
if (image) {
var reader = new FileReader();
reader.readAsDataURL(image);
let promiseOfAllDone = new Promise(res=>reader.addEventListener('loadend',res))
.then(event=>new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(resolve)
.uploadImgToDrive(name[i].value, event.target.result)
}).then(() => row[i].innerHTML='')
.catch(e=>console.error(e));
await promiseOfAllDone;//wait for all promises to be fulfilled
}
}
}

call then only after method returning promise is finished

submitTCtoDB() {
console.log("this.selectedFileList is: " + this.selectedFileList)
this.readFile().then(() => {
alert("ReadFile Finished now submit TC");
this.submitTC()
});
}
readFile() {
return new Promise((resolve, reject) => {
for (let i = 0; i < this.selectedFileList.length; i++) {
let file = this.selectedFileList[i];
alert("file in redafile" + file.name)
let fileReader = new FileReader();
fileReader.onload = () => {
this.fileContent = fileReader.result;
if (this.fileContent.indexOf("END DATA | BEGIN RESULTS") != -1) {
alert("Multiple testcases found in " + file.name + " file. Please separate/save testcases in Calc Builder. Then reimport");
const index: number = this.selectedFileList.indexOf(file);
if (index > -1) {
this.selectedFileList.splice(index, 1);
}
console.log(this.fileContent);
}
resolve(this.fileContent);
}
fileReader.readAsText(file);
}
});
}
I want to run the submitTC() method only after the readFile method is completely finished but .then(inside submitTCtoDB) is getting invoked early .
I think .then or promise is not used properly.
Desired functionality is to call the submitTC method only when readFile method is completed reading/splicing the files.
Kindly help.
You have a resolve call in a loop, but resolve only has an effect when called the first time: once a promise resolves, that is its final state, and the then callbacks are triggered. So that happens when the first file has been read, without waiting for any other files to be processed.
What you could do:
Promisify the FileReader without adding specific logic (your if check): keep that outside of it, so it remains generic
Use Promise.all to map the file list to a new promise that will give the list of file contents.
Process the list of contents for the specific checks
Return the new promise (Promise.all or the one chained on it) to the caller.
Code:
submitTCtoDB() {
console.log("this.selectedFileList is: " + JSON.stringify(this.selectedFileList))
this.readFileList(this.selectedFileList).then((validList) => {
alert("ReadFile Finished now submit TC");
this.selectedFileList = validList;
this.submitTC()
});
}
readFileList(list) {
return Promise.all(list.map(file => this.readFile(file))).then(contents => {
return list.filter((file, i) => {
const fileContent = contents[i];
if (fileContent.indexOf("END DATA | BEGIN RESULTS") != -1) {
console.log("Multiple testcases found in " + file.name + " file. Please separate/save testcases in Calc Builder. Then reimport");
console.log(fileContent);
return false; // exclude this file
}
return true; // include this file
});
});
}
readFile(file) {
return new Promise(resolve => {
console.log("file in promiseFile: " + file.name);
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader.result);
fileReader.readAsText(file);
});
}

Javascript Promise not waiting to resolve before next Then()

Anyone know why this does not run synchronically? The last promise seems to resolve before the first
...
var promise = Promise.resolve();
promise.then( () => {
return new Promise((resolve, reject) => {
var file1 = fileChooserCSV.files[0];
var reader1 = new FileReader();
reader1.onload = function(){
var csv = reader1.result;
csvJson = csvJSON(csv);
resolve();
};
reader1.readAsText(file1);
});
});
promise.then( () => {
return new Promise((resolve, reject) => {
var file2 = fileChooserConfig.files[0];
var reader2 = new FileReader();
reader2.onload = function(){
var config = reader2.result;
configJson = JSON.parse(config);
resolve();
};
reader2.readAsText(file2);
});
});
promise.then( () => {
return new Promise((resolve, reject) => {
console.log('end');
resolve();
});
});
The reader onload methods never seem to execute though they really should (there is data passed to them), and did so before they were moved to the promise. As the onload doesn't run the resolve() never fires ether to go onto the next then(), but the last then() does execute...
This code runs in a chrome extension popup if that makes any difference?
Many thanks!
UPDATE..
Restructuring it in a classic nested way works fine
var file1 = fileChooserCSV.files[0];
var reader1 = new FileReader();
reader1.onload = function(){
var csv = reader1.result;
csvJson = csvJSON(csv);
var file2 = fileChooserConfig.files[0];
var reader2 = new FileReader();
reader2.onload = function(){
var config = reader2.result;
configJson = JSON.parse(config);
console.log('end');
};
reader2.readAsText(file2);
};
reader1.readAsText(file1);
did u tryied Promise.All().then... like that
var promise = Promise.resolve(3);
Promise.all([true, promise])
.then(function(values) {
console.log(values); // [true, 3]
});
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
In JS runtime the 'then' are moved to another stack and are executed only when the call stack is empty.
For example, here you create I create a Promise.resolve() like you. But notice it's then is executed only after the entire code was executed. You will see on you screen '53' instead of expected '35':
const promise = Promise.resolve(3)
promise.then(res => {
document.write(res)
})
document.write(5)
'then' is stored in additional stack and executed only later.
For more info take a look at this perfect explanation form Barak Chemo. Watch till 30:40.
Hope it helps

Categories

Resources