Wait for function to finish running before iterating - javascript

How can I wait for the image.onload function to finish running before iterating the next value in an array??
for (let i = 0; i < Object.keys(images_list_complex_form).length; i++) {
for (let k = 0; k < images_list_complex_form[i].length; k++) {
console.log('ITERATE');
image.onload = function () {
console.log('STARTED IMAGE ON LOAD');
}
}
}
I am receiving multiple 'iterates' before receiving started image on load
As sugguested by #amadan, i have tried adding in async and await into the for loop however, after doing so the image111.onload is never triggered anymore. I never get to see the console log "Started image on load"
async function rectifyMissingSavedImages(images_list_complex_form_array,annotationIndexArray){
console.log(images_list_complex_form_array);
for(let i = 0; i < Object.keys(images_list_complex_form_array).length; i++){
for(const[index2,value2] of Object.entries(images_list_complex_form_array[i])){
if (Object.keys(annotationIndexArray).length == 0 || (!annotationIndexArray[i] || annotationIndexArray[i].includes(String(k)) == false)) {
let image_url = URL.createObjectURL(value2);
let image111 = new Image();
let canvas = new fabric.Canvas();
console.log(image111);
await new Promise((resolve,reject) =>{
image111.onload = evt => {
console.log("??");
console.log('STARTED IMAGE ON LOAD');
let background = new fabric.Image(image);
canvas.setHeight(background.height);
canvas.setWidth(background.width);
canvas.setBackgroundImage(background, canvas.renderAll.bind(canvas));
console.log("ABOUT TO RESOLVE");
image.src = image_url;
resolve();
}
});

The easiest way is to create and await a promise inside the loop. Something like:
async function loadImages() {
for (...) {
await new Promise((resolve, reject) => {
image.onload = evt => {
console.log('STARTED IMAGE ON LOAD');
resolve();
}
image.onerror = reject;
});
}
}
This is the only way that a loop can "pause" in JavaScript. Any other way involves either chaining promises, which is messy, or nesting callbacks, which is even worse.

That task is not completely trivial. At least if src is already set
(the load of the image will start as soon as you set src).
So if the src is already set you need to determine two cases:
image already loaded
is still loading
This can be check with with HTMLImageElement.complete
Furthermore, you need to check for both the load or error case otherwise your loop might get stuck in case of a network error or when something else goes wrong with loading the image.
// create a function to which you can pass the image
// that function will determine if the image was already loaded
// or it has to wait for the image to be loaded
function waitForImage(image) {
return new Promise((resolve, reject) => {
// first check if image was already loaded
if (image.src && image.complete) {
// check if loading was successful
// there might be better ways then checking its dimension
// because this will fail if you load an image that has no size (like tracking pixels)
if (image.width > 0 || image.height > 0) {
resolve()
} else {
reject()
}
} else {
// resolve in case a successful load
image.onload = resolve
// reject in case of an error
image.onerror = reject
}
})
}
async function loadImages() {
// create some dummy data to make the snipped runable
let urls = [
'https://via.placeholder.com/350x150',
'https://via.placeholder.com/350x150',
'https://via.placeholder.com/350x150',
'https://via.placeholder.com/350x150',
'https://via.placeeeeeeeholder.com/350x150', // intentional typo to make it fail
'https://via.placeholder.com/350x150'
]
let images = urls.map(url => {
let image = new Image()
image.src = url
return image
})
// iterat over the images
for (let image of images) {
try {
// call a function that returns a Promise
// and use await wait for the image to load or fail loading
await waitForImage(image)
console.log(`loading ${image.src} finished`)
} catch (err) {
console.log(`loading ${image.src} failed`)
}
}
}
loadImages();

Related

Why is async function slowing down my image loading process?

I have written an image-loading function like this:
export function loadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
if (image_map.has(url)) {
resolve(image_map.get(url));
} else {
let image: HTMLImageElement = new Image();
if (typeof window.static_url != 'undefined' && url[0] == '/') {
image.src = `${window.static_url}${url}`;
} else {
image.src = url;
}
image_map.set(url, image);
image.crossOrigin = 'Anonymous'; // Prevent canvas getImageData CORS issue
image.onload = function () {
resolve(image);
};
image.onerror = reject;
}
});
}
I have about 200 images to load, and prior to asking this question, I have been writing code like this:
for (let url of image_list) {
await loadImage(url);
}
And it took me ages (16s or more) to load all these images.
Today I decided to remove the await from my code, and miracle happened: the image loading process finished within 2s. How is that even possible? I thought javascript is single-threaded, and I had expected that, by removing await, the image loading should simply take place after the rest of my code, instead of taking place before, but the total time for them to load should not differ so greatly.
Your current code loads the images serially (one after the other).
You can instead execute them in parallel using promise all.
// 1. simultaneously kick off load image on each url
const promises = image_list.map(loadImage);
// 2. await them all
await Promise.all(promises);
// 3. Images have loaded.
Or more succinctly.
await Promise.all(image_list.map(loadImage));

Javascript promise. Why does resolve return different values when it's called in different places?

const image = document.querySelector('.images');
const createImage = (imgPath, seconds) => {
return new Promise(function (resolve, reject) {
if (imgPath) {
const img = document.createElement('img');
setTimeout(() => {
img.src = imgPath;
image.append(img);
resolve(img);
}, 1000 * seconds);
} else reject('ERROR. REJECTED');
});
};
let currentImage;
createImage('./img/img-1.jpg', 1)
.then(img => {
console.log(img);
// img.style.display = 'none';
console.log('1 successful');
// img.style.display = 'none';
return createImage('./img/img-2.jpg', 2);
})
.then(img => {
console.log(img);
img.style.display = 'none';
console.log('2 successful');
return createImage('./img/img-3.jpg', 3);
})
.then(img => {
img.style.display = 'none';
console.log('3 successful');
return img;
});
I'm trying to display first picture then hide it to display a second picture after 2 seconds later. Then, hide the second picture to display a third picture.
When I use resolve(imp) inside of setTimeout function, it returns <img src="./img/img-1.jpg>.
When resolve(img) is placed outside of setTimeout, it returns just <img>. Why are each case return values different? because in JS, we can still use values that was called inside of function even after they are executed and disappeared.
setTimeout(() => {
img.src = imgPath;
image.append(img);
}, 1000 * seconds);
resolve(img);
The root cause of the difference is: do you add the src first and then log, or do you log first and then set the src. In both cases, the browser will log the element as it appears at the time of the log statement (at least in chrome -- different browsers may log slightly differently). If the element has an src, the log reflects that. If the element has no src yet, the log has no src.
This can be demonstrated without involving promises at all:
const img = document.createElement('img');
console.log(img);
img.src = 'example';
const img = document.createElement('img');
img.src = 'example';
console.log(img);
So adding the promises back in: if you resolve the promise before you've added an src, code execution moves to the .then callback and logs it out, with no src. A second later, the src gets added, too late to be included in the log. If instead, you wait until the src has been added and then resolve the promise, then the code in the .then callback can log out the src.

How to stop a loop that creates promises when any one of them fails?

I am trying to download some files that come split into multiple parts, each file has a N number of parts between 1 and 9999. I need to wait for every file to download to then process them together.
const partsPromises = [];
let finished = false;
for (let i = 1; i < 10000 || finished; i++) {
const part = String(i).padStart(5, '0'); // Part numbers are formated with 5 digits
const prom = downloadPart(id, part); // Consider I'm given this id before this code by the user
prom.catch(() => finished = true);
/* This doesn't work as intended, I would like
for the first promise to fail to inmediately stop the loop */
partsPromises.push(prom);
}
Promise.allSettled(videoPartsPromises).then(() => {
// After all promises have settled I process the files
});
function downloadPart(videoId, partNum) {
return new Promise((resolve, reject) => {
console.log(`Downloading part ${partNum}`);
https.get(
`PART URL`,
(response) => {
if (response.statusCode === 403) {
reject();
return;
}
const file = fs.createWriteStream(`${partNum}.file`);
response.pipe(file);
resolve();
}
);
});
}
Any ideas on how could I achieve this behaviour?

How can I show error message after select photo?

I have a problem with my component InputAvatar - https://codesandbox.io/s/721n5346x6
How Can I show error message after select photo, if size of photo is not 200x200 px?
I think that problem is with img.onload = function(), because error message is not returned in time.
Thank you advance
You're right that the onload does not return in time. This is an asynchronous event callback, so it fires only after the image finishes loading. This results in an empty array value returned from getErrors, because it is executed sooner than the image loads.
In order to make the code work, you'll have to introduce some async aware code, eg. using promises.
getErrors(value = this.state.file || "") {
// Return a single promise and move the function execution inside.
return new Promise((resolve, reject) => {
const errors = []
const img = new Image();
img.src = value;
img.onload = function() {
var w = img.width;
var h = img.height;
errors.push("error");
if (w != 200 || h != 200) {
errors.push("The photo must be 200 x 200 px.");
}
// Resolve the pending promise with the errors value letting callers know it's ready.
resolve(errors)
};
})
}
This way you'll be able to wait for the outcome of the image loading and use it in a way you need it.
validate(value = this.state.file || "") {
this.getErrors(value)
// Wait for the errors and only then set the state.
.then(errors =>
this.setState({
errors
});
)
}

Unchecked runtime.lastError while running tabs.captureVisibleTab: Failed to capture tab: unknown error

I'm trying to take screenshots of all tabs and compress them in my chrome extensions. My problem is in this error: Unchecked runtime.lastError while running tabs.captureVisibleTab: Failed to capture tab: unknown error at Object.callback in line of code chrome.tabs.captureVisibleTab(function(screenshotUrl). This error appears arbitrarily.
Here is the program code:
var arr = [];
var i;
var n;
var TAB;
function allScreen(){
var currentTab = TAB[i];
chrome.tabs.update(currentTab.id, {selected: true}, function (){
chrome.tabs.captureVisibleTab(function(screenshotUrl) {
var newImage = new Image();
newImage.src = screenshotUrl;
newImage.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 300;
var ctx = canvas.getContext("2d").drawImage(newImage, 0, 0, newImage.width, newImage.height, 0, 0, canvas.width, canvas.height);
var data = canvas.toDataURL('image/jpeg', 1)
arr.push(data);
i++;
if (i < n){allScreen(TAB);}
}
});
});
}
function screenShot(){
arr = [];
chrome.tabs.getAllInWindow(null, function(tabs){
i = 0;
n = tabs.length;
TAB= tabs;
allScreen(tabs, function(){
console.log("finish");
});
});
}
Can anyone know what the problem is?
I've had security issues trying to convert <img src> to data URI using a <canvas> like this in injected scripts - I'm not sure why you need to as the output of chrome.tabs.captureVisibleTab is already a data URI.
I'm a little confused by the parameters passed to allScreen as they're never referenced from inside it - instead you're switching to global variables.
You can make this a lot easier with promises. First refactor allScreen to become a promise that resolves when the screenshot has been taken:
function screenshotTab(currentTab){
return new Promise((resolve, reject) => {
chrome.tabs.update(currentTab.id, {selected: true}, () => {
if (chrome.runtime.lastError)
// Return as an error for the awaited catch block
reject(`Error selecting tab ${chrome.runtime.lastError}`));
else
chrome.tabs.captureVisibleTab(uri => {
if (chrome.runtime.lastError)
reject(`Error getting screenshot ${chrome.runtime.lastError}`));
else
resolve(uri);
});
});
});
}
Another promise can give us the tabs:
function getTabsInWindow() {
return new Promise((resolve, reject) => {
chrome.tabs.getAllInWindow(null, tabs => {
if (chrome.runtime.lastError)
reject(`Error getting tabs in window ${chrome.runtime.lastError}`));
else
resolve(tabs);
});
});
}
Then we can use map to apply it to all the tabs:
async function screenShotAll() {
// We can use await to get the result of the promise
const tabs = await getTabsInWindow();
// map the collection of tabs into promises waiting for screenshots
const runningScreenshots = tabs.map(screenshotTab);
// Use await with Promise.all to wait for them all to call back
const result = await Promise.all(runningScreenshots);
console.log('done', result);
return result;
}
This function now returns a promise, so you can do:
// async-await syntax
const finalResult = await screenShotAll();
// Or promise syntax works too
screenShotAll().then(finalResult => { /* deal with result */ });
Finally you can use a library (such as chrome-extension-async) to create all the promise wrappers and chrome.runtime.lastError checking for you.

Categories

Resources