Returning an Image only Once Loaded - javascript

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

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

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.

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.

JavaScript: Continue execution when image is loaded

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

Categories

Resources