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
});
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
}
}
}
I'm opening a file to read contents like so:
convertBlobToBase64(blob){
var convertPromise = new Promise(function(resolve, reject){
var fileReader = new FileReader();
fileReader.onload = function() {
var dataUrl = this.result;
var base64 = dataUrl.split(',')[1];
resolve(base64);
};
fileReader.readAsDataURL(blob);
});
return convertPromise;
}
I then call this function and pass the result data when it resolves:
myFunction(audioFile){
var to64 = this.convertBlobToBase64(audioFile);
to64.then(function(base64Val){
var nextPromise = postCall();
nextPromise.then(//stuff);
return nextPromise;
});
return to64;
}
However, when I call myFunction, it immediately returns a resolved promise that includes the converted data from convertBlobToBase64, and not an unresolved promise that should be waiting on nextPromise as expected.
Instead, myFunction's .then is called immediately and fails as it doesn't have the correct data. Am I misunderstanding the Promise function?
Try this code:
myFunction(audioFile){
var to64 = this.convertBlobToBase64(audioFile);
return to64.then(function(base64Val){
var nextPromise = postCall();
return nextPromise.then(//stuff);
});
}
Btw you dont need to wrap another promise to a function. You can use your postCall as resolve func and chain it like this:
myFunction(audioFile){
return convertBlobToBase64(audioFile)
.then(base64Val => postCall())
.then(//stuff)
}
I'm pulling data from 3 differents APIs, and I want to merge all these results into one array.
I guess the proper way to do this is to use Promises:
var function1 = new Promise((resolve, reject)=>{
...
resolve();
});
var function2 = new Promise((resolve, reject)=>{
...
resolve();
});
var function3 = new Promise((resolve, reject)=>{
...
resolve();
});
Promise.all([function1, function2, function3]).then(function(values){
// Values are all here!
});
How can I call all the promises again and join them via Promise.all every second?
I've tried
setInterval(function(){
Promise.all([function1, function2, function3]).then(function(values){
// Values are all here and up to date!
});
}, 1000)
without success.
Thanks for your help!
You need to recreate the Promise objects every time you want to invoke them:
var function1 = (resolve, reject)=>{
console.log('calling 1');
resolve();
};
var function2 = (resolve, reject)=>{
console.log('calling 2');
resolve();
};
var function3 = (resolve, reject)=>{
console.log('calling 3');
resolve();
};
setInterval(function(){
Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
console.log('alldone');
});
}, 1000)
This is because the promise is only executed upon creation, and otherwise in your loop you're just attaching a new then() method which will not call your API.
EDIT:
Be advised that setInterval, as shown, will fire three requests to your API every 1 second. That's a pretty fast rate of fire and is likely to get you in trouble unless both your API and the network are blazing fast.
A more sensible approach might be to only fire the next request once the previous one has been handled.
To do that, simply substitute the setInterval call with this:
var callback = function(){
Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
console.log('all done');
setTimeout(callback, 1000);
console.log('next call successfully enqued');
});
};
setTimeout(callback, 1000);
Thanks to Kevin B for pointing this out.
Make sure you call the API each time (by creating new Promise).
/**
* Create function that return a NEW Promise each time.
* Once resolved a promise won't change state
*/
const function1 = () => new Promise((resolve, reject)=>{
// something
resolve('1 - ' + new Date());
});
const function2 = () => new Promise((resolve, reject)=>{
// something
resolve('2 - ' + new Date());
});
const function3 = () => new Promise((resolve, reject)=>{
// something
resolve('3 - ' + new Date());
});
/**
* For the setInterval callback, create a function
* that will return a new Promise from all the previous promises
*/
const all = () => Promise.all([
function1(),
function2(),
function3()
]).then(function(values){
console.log(values);
return values;
});
setInterval(all, 1000);
Answer
This question was in my interview and I had trouble that it should be implemented only using class, maybe you will find it useful for you and rewrite it using functions.
class HTTPService {
constructor(base = "", strategy = "default" | "queue", promises = []) {
this.base = base;
this.strategy = strategy;
this.promises = 0;
}
urlSerializer(payload) {
return `?${Object.entries(payload)
.map((el) => el.join("="))
.join("$")}`;
}
returnDefaultPromise(path) {
return new Promise((resolve) =>
setTimeout(() => {
resolve(path);
}, 1000)
);
}
returnQueuePromise(path) {
return new Promise((resolve) =>
setTimeout(() => {
this.promises -= 1000;
resolve(path);
}, this.promises)
);
}
get(url, payload) {
let serialized = payload ? this.urlSerializer(payload) : "";
if (!url) throw new Error("Please add url to function argument");
switch (this.strategy) {
case "default":
return this.returnDefaultPromise(`${this.base}/${url}${serialized}`);
case "queue":
this.promises += 1000;
return this.returnQueuePromise(`${this.base}/${url}${serialized}`);
default:
return new Promise((resolve) =>
resolve(`${this.base}/${url}${serialized}`)
);
}
}
}
const httpService = new HTTPService("http://test.com", "queue");
const httpService2 = new HTTPService("http://test.com", "default");
const br = document.createElement('br');
let div = document.createElement('div');
let content = document.createTextNode('');
httpService.get("/api/me").then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
});
// should be 'http://test.com/api/me'
// B:
httpService.get("/api/test", { foo: 1, test: 2 }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com/api/test?foo=1&test=2'
});
// C:
httpService.get("/api/test", { baz: 10, case: "some" }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com//api/test?baz=10$case=some'
});
// D:
httpService.get("/api/test", { bar: 1, dummy: "text" }).then((data) => {
content = document.createTextNode(data);
div.appendChild(content);
div.appendChild(br);
console.log(data);
// should be 'http://test.com//api/test?bar=1$dummy=text'
});
httpService2.get("/app/test").then((data) => {
content = document.createTextNode(data);
div.appendChild(br);
div.appendChild(content);
div.appendChild(br);
console.log(data);
});
document.querySelector('#result').appendChild(div)
<div id="result"></div>
This example to call functions
Also you can check analogy in react application through codesandbox
An alternative to setInterval() is using setTimeout() inside a recursive loop:
function joinedResults() {
Promise.all([function1, function2, function3])
.then(function(values){
console.log(values);
setTimeout(()=>{ joinedResults() }, 1000);
});
}
joinedResults();
This stackoverflow post has a similar question.
In order to chain your promise outside of setInterval, you can wrap it in a function:
let repeat = (ms, func) => new Promise(r => (setInterval(func, ms), wait(ms).then(r)));
repeat(1000, () => Promise.all([myfunction()])
.then(...)
Solution
Every time you need to create promises again and resolve them.
setInterval(function(){
var function1 = new Promise((resolve, reject)=>{
resolve(new Date());
});
var function2 = new Promise((resolve, reject)=>{
resolve(2);
});
var function3 = new Promise((resolve, reject)=>{
resolve(3);
});
Promise.all([function1, function2, function3]).then(function(values){
console.log(values);
});
}, 1000)
Run the above code its working!!!
I have a promise and I want it to resolve only when inner promise has resolved. Right now it resolves before the "resolve" function has been reached in the "loadend" callback.
What am I missing? I am confused about the way you are supposed to use resolve and about how you can use a promise within another promise.
I couldn't find anything that helped on the web.
In the following example I basically load a bunch of files, for each file I get a blob and I want to pass this blob in a file reader.
Once all files have been passed to the file reader, I want to move to the next function in the promise chain.
Right now it goes to the next function in the chain without waiting for resolve to be called.
var list = [];
var urls = this.files;
urls.forEach(function(url, i) {
list.push(
fetch(url).then(function(response) {
response.blob().then(function(buffer) {
var promise = new Promise(
function(resolve) {
var myReader = new FileReader();
myReader.addEventListener('loadend', function(e) {
// some time consuming operations
...
window.console.log('yo');
resolve('yo');
});
//start the reading process.
myReader.readAsArrayBuffer(buffer);
});
promise.then(function() {
window.console.log('smooth');
return 'smooth';
});
});
})
);
});
...
// run the promise...
Promise
.all(list)
.then(function(message){
window.console.log('so what...?');
})
.catch(function(error) {
window.console.log(error);
});
When you don't return anything from then callbacks, it assumes synchronous operation and goes to resolve the result promise with the result (undefined) immediately.
You need to return a promise from every asynchronous function, including then callbacks that you want to get chained.
Specifically, your code should become
var list = this.files.map(function(url, i) {
// ^^^^ easier than [] + forEach + push
return fetch(url).then(function(response) {
return response.blob().then(function(buffer) {
return new Promise(function(resolve) {
var myReader = new FileReader();
myReader.addEventListener('loadend', function(e) {
…
resolve('yo');
});
myReader.readAsArrayBuffer(buffer);
}).then(function() {
window.console.log('smooth');
return 'smooth';
});
})
});
});
or even better, flattened:
var list = this.files.map(function(url, i) {
return fetch(url).then(function(response) {
return response.blob();
}).then(function(buffer) {
return new Promise(function(resolve) {
var myReader = new FileReader();
myReader.addEventListener('loadend', function(e) {
…
resolve('yo');
});
myReader.readAsArrayBuffer(buffer);
});
}).then(function() {
window.console.log('smooth');
return 'smooth';
});
});