JavaScript: Continue execution when image is loaded - javascript

I am loading 8 images in a loop, and I want to pause the loop for each image to wait until it's loaded. So instead of:
image = new Image()
image.onload = function(){ /*do anything*/ }
image.src = "abc.svg"
I want something like:
image = new Image()
image.src = "abc.svg"
image.waitLoad()
//do anything
Is there a way to do this using Javascript?
Thanks in advance.

You can't pause. However you can delay next image load until previous is loaded. For example:
var images = ['path1', 'path2', 'path3', 'path4'];
(function next(images) {
if (images.length) {
var img = new Image();
img.onload = function() { next(images); };
img.src = images.shift();
}
})(images.slice());
In this case it will load next image only after the previous is fetched. If there are chances that image src might be broken, make sure you also handle, e.g. img.onload = img.onerror = function() { next(images); };.
var images = [1, 2, 3, 4];
(function next(images) {
if (images.length) {
var img = new Image();
img.onload = function() {
document.body.appendChild(img);
setTimeout(function() { next(images); }, 500);
};
img.src = images.shift();
}
})(images.slice());
<base href="http://lorempixel.com/100/100/food/">

You may want to consider using a Deferred object so that you can execute when the images are ready.
There are several libraries with deferred promises available in them. For simplicity here is an example with jquery. There may be small errors, I scripted this from memory.
var arrayOfImages = ["image1.png", "image2.png", "image3.png", "image4.png"];
var isLoaded = function(img)
{
var deferred = $.Deferred(); // Deferred Promise Object
var imgObj = $("<img src='"+img+"'/>");
if(imgObj.height > 0 || imgObj.width > 0) // image is already loaded
{
deferred.resolve(true); // Resolving the Promise triggers any function assigned to done to execute.
}
else
{
imgObj.on("load", function(e)
{
deferred.resolve(true); // Resolving the Promise triggers any function assigned to done to execute.
});
window.setTimeout(function() // Timeout in case the image takes to long to execute
{
if(deferred.status() !== "resolved") // This maybe be deferred.state() in jquery
{
console.warn("isLoaded Failed");
deferred.reject(false); // Rejecting a promise will cause all methods assigned to fail to execute.
}
}, 7500);
}
return deferred.promise();
};
var deferredArray = [];
var len = arrayOfImages.length;
for(var i=0;i<len;i++)
{
var imagePromise = isLoaded(arrayOfImages[i]);
}
$.when(imagePromise, function()
{
// All Images Loaded
});
If you want to just check one image
isLoaded(arrayOfImages[2]).done(function(result) // checking only image 2 of the array
{
// this image is loaded
}).fail(function(errorObj)
{
// this image failed to load
});

Try to use images loading with Promise
deferimg.js

Related

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

Returning an Image only Once Loaded

I want to return an image from a JavaScript function, but only once said image is loaded. I've been running into problems by returning not loaded images from this function
function modifyImage(src) {
//do some stuff to src
let dest = new Image()
dest.src = "modified code, whatever"
return dest
}
By the time the function returns dest, dest is not yet loaded. How can I wait for dest to load?
You want the load event.
Note: This is async. That means the file will load AFTER the function has finished, and you will have to use the call back.
function modifyImage(src) {
//do some stuff to src
let dest = new Image()
dest.addEventListener("load", () => {
console.log("Loaded");
});
dest.src = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
return dest
}
modifyImage();

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.

dealing with async code on frontend

I'm pretty new to programming with JS and started with Node. I have been using the async module while working on the backend with the database to deal with async code. What should I be using to deal with async code on the front end. I have the following that checks an array of images to see if they load.
$(document).ready(function () {
var images = <%- JSON.stringify(images) %>;
var imagesNotLoading = [];
images.forEach(function (image) {
if(!imageLoads(image)) {
imagesNotLoading.push(image)
});
console.log(imagesNotLoading);
//returns true if image loads successfully
function imageLoads(src) {
var img = new Image();
img.onload = function() {
return true;
};
img.onerror = function() {
return false;
};
img.src = src;
}
});
The imageLoads function will return undefined or !undefined which is true. What should I be using to wait till the function has returned true or false before pushing it on to the imagesNotLoadingArray. Any help appreciated.
You should write the imagesNotLoading array on the error handler.
var images = <%- JSON.stringify(images) %>;
var imagesNotLoading = [];
images.forEach(function (image) {
imageLoads(image)
});
//returns true if image loads successfully
function imageLoads(src) {
var img = new Image(src);
img.onload = function() {
successFunction(img);
};
img.onerror = function() {
errorFunction(img);
};
img.src = src;
}
function errorFunction(img){
imagesNotLoading.push(img.src);
}
function successFunction(img){
console.log("Image "+img.src+" was loaded successful!");
}
If you're using the async-library with node, there is no reason why you shouldn't use the same in your browser to deal with asynchronous code. As soon as you load the browser version of the async library, you can use its async.parallel(...) function to check which of your images are loading and which aren't.
Apart from that, you might wanna take a look at promises, since they greatly simplify dealing with asynchronous code in Javascript - no matter whether in the front- or backend.
Using promises, your code would look something like
$(document).ready(function () {
var images = <%- JSON.stringify(images) %>;
var imagesNotLoading = [];
images.forEach(function (image) {
imageLoads(image).then(function(){}, function() {
imagesNotLoading.push(image);
});
});
// returns true if image loads successfully
function imageLoads(src) {
var img = new Image();
return new Promise(function(resolve, reject) {
img.onload = resolve;
img.onerror = reject;
img.src = src;
});
}
});
This will wait for all your images to either be loaded or errored and execute the callback afterwards. Since the onload and onerror functions are executed at some time in the future and you don't know yet when this is going to be, you're best of with promises to handle the result of image loading.

Categories

Resources