How can I show error message after select photo? - javascript

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

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.

Wait for function to finish running before iterating

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();

Wait for images to load after creating them via an AJAX request

I am getting images (74 files) with an AJAX request. I am loading them in to an array. Then I change some style calling the function changeMargins(). The issue is that the function is called when the images are not yet all loaded and ready.
imageArray = new Array();
$.ajax({
url: url,
success: function (data) {
counter = 0
$(data).find("a").attr("href", function (i, val) {
if (val.match(/\.(jpe?g|png|GIF)$/)) {
imageArray[counter]= new imageItem(url + val)
++counter;
}
});
changeMargins(imageArray);
How can I wait for completion of the AJAX and all load events on the Image elements and then continue processing?
This problem is probably best addressed by "promisifying" the image loading process, ie create a promise that resolves when the images have loaded.
There's a design decision to be made ... whether (a) to swallow load errors or (b) to allow any individual error to cause the whole process to fail.
Assuming (a), you would write something like this :
function loadImages() {
return $.ajax({
url: url,
// other ajax params?
}).then(function(data) {
return $.Deferred(function(dfrd) { // promisify the entire image loading proceess
var imageArray = [];
var counter = 0; // tally of images that have either loaded or failed to load
$(data).find('a').get().map(function(element) {
// map a elements in data to an array of their hrefs
return element.href;
}).filter(function (href) {
// filter out any non-jpe?g|png|GIF
return href.match(/\.(jpe?g|png|GIF)$/);
}).forEach(function(val) {
// for each match, create a Image() object, push onto the array, attach onload and onerror handlers, and set the `src` attribute.
var img = new Image();
imageArray.push(img);
img.onload = function loadHandler() {
counter += 1;
if(counter == images.length) {
dfrd.resolve(imageArray);
}
};
img.onerror = function errorHandler() {
console.log(new Error('image ' + url + val + ' failed to load'));
// Here, you might choose to splice failed Images out of `imageArray`.
counter += 1;
if(counter == images.length) {
dfrd.resolve(imageArray);
}
};
img.src = url + val;
});
});
}).then(function(imageArray) {
changeMargins(imageArray);
return imageArray;
});
}
Notes:
More typically, one would choose to promisify at the lowest level (ie each Image() individually) but promises are fairly expensive, therefore with 74 images, it's better to promisify en masse.
By promisifying, and returning a promise from loadImages(), its caller is informed of completion of the process - you can chain loadImages().then(...) and do stuff when the images have loaded/failed.

How to load images with chained Promises

I'm having trouble loading images with a script of chained promises - they turn up undefined by the end. I also have a 404 error I don't quite understand as I've confirmed that all the sources are valid, but admit I'm still a noob to debugging in an async context. Here is the fiddle in question. I've been chewing on this one for a couple days now and could use a nudge or two to get me headed in the right direction. I think this may be the problem area:
function loadImagePromises() {
var imageLoadPromises = [];
for ( var i = 0; i < sources.length; i++ ) {
imageLoadPromises.push(
imgLoad( sources[i] ).then(function(response) {
var myImage = new Image();
myImage.src = response; // response is a blob
}, function(Error) {
console.log("There was an error when the image was loaded after download: " + Error);
}) );
}
console.log( imageLoadPromises );
return imageLoadPromises;
}
As context, I'm writing an image loader script using promises for a Three.js program I have. No need to load the images to the DOM - I'll use them later as textures in a WebGL visualization.
NOTE:
Here's an earlier and simpler fiddle working end-to-end and outputting to the DOM.
Probably some other considerations here but your success handler isn't returning anything explicitly, so it's implicitly returning undefined:
function(response) {
var myImage = new Image();
myImage.src = response;
// if you want the promise to resolve to a useful value put
// it below, with whatever you want e.g:
return myImage;
}
RE: Errors:
You should probably not shadow Error in your error handler, just use lowercase error. Also, when using console.log/console.error you can use , to chain parts of the message which usually presents a richer message than the string used when concatenating.
e.g.
console.error("There was an error when the image was loaded after download: ", error);
FWIW you could also reduce some of the tedious iteration/collection here by using map to map each source to a promise:
function loadImagePromises() {
return sources.map(function(source) {
return imgLoad(source).then(function(response) {
// ...
});
}
EDIT Re: waiting for image objects to have actually loaded
return imgLoad(source).then(function(response) {
var imageURL = window.URL.createObjectURL(response);
var myImage = new Image();
return new Promise(function(resolve, reject) {
myImage.onload = function () {
// image has loaded
resolve(myImage);
}
// TODO error handling
myImage.src = imageURL;
});
}, console.error);
});

Categories

Resources