Awaiting .map execution with Image processing - javascript

Hi so I asked this before but couldn't get an answer so was hoping this time someone would be kind enough to help out. I basically have a dropzone where i drop mulitple images then when i click on Submit i have a handlesubmit function. That function loops throught all the images and converts them to baseURL format using the FileReader() API. The problem is that once its all mapped and pushed into array i want to access that array and do stuff with it but right now the code isn't awaiting the .map execution and jumping straight to a console.log afterwards thats logging the array which is empty obviously. below is what i tried to do using promises but still couldn't get it to work:
const functionWithPromise = async file => {
let image = '';
let reader = new FileReader();
reader.readAsDataURL(file.file);
reader.onload = await function (){
image = reader.result;
console.log(image);
setArr([...arr,image]);
};//a function that returns a promise
reader.onloadend = function(){
Promise.resolve('ok');
}
};
const anAsyncFunction = async file => {
return functionWithPromise(file)
};
const getData = async (files) => {
return Promise.all(files.map(file => anAsyncFunction(file)))
};
//Clicking submit after all files are dropped triggers this.
const handleSubmit = (files, allFiles) => {
getData(files).then(r => console.log(arr))
.catch(err=>console.log(arr))
//I want to do some stuff here with the arr AFTER the mapping and pushing base64 values is complete.
};

Related

Check array after async task finishes

Today I'm having an issue with async task while using JSZIP.
I want to check the array content after the async task executed by JSZip ends.
I have a zip which contains one XML file which I read and get a specific node to store them in another list to later do some stuffs. Well, my issue is that the checking array is called before the XML file is read and, as it is executed before XML file is read, array is empty.
I tried some ways to make it work, but, without success yet.
fileElement.addEventListener('change', (e) => {
try {
var zip = new JSZip();
zip.loadAsync( fileElement.files[0])
.then(function(zip) {
let xmlfiles = []
const _ziptask = async () => {for(let [filename, file] of Object.entries(zip.files)) {
if (filename.includes("file.xml")) {
file.async("string").then(function (data) {
let xmlDoc = new DOMParser().parseFromString(data,"text/xml");
let metaInputs = [...xmlDoc.querySelectorAll("file")];
xmlfiles = metaInputs.filter(_node => null != _node.getAttribute('src'));
console.log("FILE.XML LOOP ENDED")
});
}
}}
async () => {
await _ziptask().then(() => {
console.log("CHECKING FILE.XML ARRAY ")
console.log(xmlfiles)
})
}
}, function() {console.error("ERROR: NOT ZIP FILE")});
} catch (error) {
restoreFileInput("Something went wrong, try it again later")
}
});
Well, basically after testing different things, I reached the goal by using an array of promises and using Promise.all, which basically check that all the promises were resolved successfully.
Its curious that where I read this, the promises are stored in a const declaration instead var or let.
Anyway, if someone want to see the result:
fileElement.addEventListener('change', (e) => {
try {
var zip = new JSZip();
zip.loadAsync( fileElement.files[0])
.then(function(zip) {
let xmlfiles = []
const promises = [];
for(let [filename, file] of Object.entries(zip.files)) {
if (filename.includes("file.xml")) {
promises.push(file.async("string").then(function (data) {
let xmlDoc = new DOMParser().parseFromString(data,"text/xml");
let metaInputs = [...xmlDoc.querySelectorAll("file")];
xmlfiles = metaInputs.filter(_node => null != _node.getAttribute('src'));
console.log("FILE.XML LOOP ENDED")
}));
}
}
Promise.all(promises).then(function () {
console.log("CHECKING FILE.XML ARRAY ")
console.log(xmlfiles)
});
}, function() {console.error("ERROR: NOT ZIP FILE")});
} catch (error) {
restoreFileInput("Something went wrong, try it again later")
}
});
Thanks for the help to the guys who commented previously.
Best regards.

async function and updating global variable

im trying to load multiple images using an async function and a regular for function, and for some reason only the last image of the called is being loaded onto the dom..
I think it has to do with the global variable however I cant quite get a grip on why.
let imgEl;
const imageContainer = document.querySelector('.images');
function createImage(imgPath) {
return new Promise((resolve, reject) => {
imgEl = document.createElement('img');
imgEl.src = imgPath;
imgEl.addEventListener('load', () => {
document.querySelector('.images').append(imgEl);
resolve(imgEl);
});
imgEl.addEventListener('error', e => reject(e));
});
}
async function loadAll(...imgPaths) {
try {
const res = await Promise.all(
imgPaths.map(async imgPath => await createImage(imgPath))
);
res.forEach(el => el.classList.add('paralell'));
} catch (error) {
console.log(error);
}
}
loadAll('/img/img-1.jpg', '/img/img-2.jpg', '/img/img-3.jpg');
By the time the load event on any of the images has fired, the value of imgEl been overwritten by the last call to imgEl = document.createElement('img');.
This means the last image gets appended multiple times (initially adding it to the DOM and then moving it from where it is to … well … the same place several times).
The earlier images get garbage collected as there are no references left to them.
Define imgEl inside the function you pass to new Promise; don't make it a global. It doesn't need to be shared between multiple functions or multiple invocations of the same function.

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

React useState hook returns old state value despite it being updated

I want to:
upload an image into the browser (I do this with React Dropzone. My uploadImage function below runs onDrop of file into Dropzone.)
read the image as an actual image using the FileReader API.
save the image file as well as image metadata into state, using React Hooks.
make an API call that pushes this image to a server.
I am trying to confirm that I have the image at step 4, but I cannot confirm that via my console messages. I'm somehow stuck with the original state values despite log messages showing the state has already been updated.
I've tried adding extra asyncs to everything, and I've wrapped stuff in promises. I can confirm the readImage function is fine. I can confirm the run sequence is readImage, saveImage, setGraphic within saveImage, and then the console.log in uploadImage which should have the updated values but doesn't.
const [graphic, setGraphic] = useState({ ...nullGraphic });
const readImage = async (img) => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onerror = () => reader.abort();
reader.onabort = () => reject(new DOMException('Problem parsing input file.'));
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(img);
});
};
const saveImage = async (img) => {
const imageRead = await readImage(img);
console.log(imageRead);
await setGraphic({
...graphic,
image: imageRead,
imageName: img.name,
imageType: img.type,
});
};
const uploadImage = async (img) => {
await saveImage(img);
console.log(graphic);
});
render() {
console.log(graphic);
return( <some dom> )
}
this is my strange console sequence:
Form.js:112 starting saveImage
Form.js:95 (snipped value of imageRead)
Form.js:237 {snipped updated values of graphic}
Form.js:114 done with saveImage
Form.js:116 {snipped original values of graphic}
My Theory
The useState hook is doing something funky.
The process copies the value when it starts instead of reading it whenever the console.log is being read, so the uploadImage function gets the original values of the useState hook for the lifetime of that process.
It might become clear why this would not work if we would simplify all those React magic into :
function withState(initial, fn) {
function setState(updated) {
setTimeout(() => fn(updated, setState), 0);
//...
}
setState(initial);
}
Now we can illustrate what happens:
withState(0, (counter, setCount) => {
console.log("before", counter);
if(counter < 10) setCount(counter + 1);
console.log("after", counter);
});
As you can see, setCount will not change the state (count) directly. It will actually not change the local count variable at all. Therefore "before" and "after" will always log the same (as long as you don't mutate counter directly).
Instead calling setCount will be deffered (and batched in React), and then it will recall the whole component function passing in the new count.
Therefore it makes no sense trying to await the setState call, also it makes no sense to log the state before and after it (as it won't change inbetween). Log it once in a component, if you set the state the whole component executes again and the logging will log the new state.

Why is my Observer subscriber not running if I call it after I call an async function?

I have some code that looks like this:
async function promptHandler(source) {
source.subscribe(function(line) {
console.log(`line == ${line}`);
});
let matchingTests = await getMatchingTests('ROGL');
}
This prints out the contents of the source Observable, which is listening to a ReadStream of a txt file. When the function as it is above is called, I see the output of the file. However, if I call subscribe() after getMatchingTests() gets called, like this:
async function promptHandler(source) {
let matchingTests = await getMatchingTests('ROGL');
source.subscribe(function(line) {
console.log(`line == ${line}`);
});
}
I don't see the contents of the txt file. I know that the matchingTests variable contains the successful results of getMatchingTests, so I don't think it's preventing Node from executing that line.
I'm guessing that something about the getMatchingTests async function call is messing with the source Observable, but I'm not seeing how.
Here's my source code:
let fileStream = createReadStream(file)
.pipe(split());
let source = new Observable(o => {
fileStream.on('data', line => {console.log('data'); o.next(line);});
fileStream.on('error', err => o.error(err));
fileStream.on('end', () => {console.log('end'); o.complete();});
});
My intuition here is that the source observable is a hot source, and that by the time await has returned with the matching tests, your text file is already read. So when you subscribe at that point, there is no line to read, they were read before you subscribed to the source.
UPDATE :
Given your code, if the ordering is a problem for your use case, you can consider moving the filestream creation into the observable factory, i.e.
let source = new Observable(o => {
let fileStream = createReadStream(file)
.pipe(split());
fileStream.on('data', line => {console.log('data'); o.next(line);});
fileStream.on('error', err => o.error(err));
fileStream.on('end', () => {console.log('end'); o.complete();});
});
That way, the stream will be created and started only when you subscribe.

Categories

Resources