dealing with async code on frontend - javascript

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.

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

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

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

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

My jquery $.Deferred is not preventing another $.ajax call from happening until resolved

I am using long polling to retrieve a large json feed with image URLs. I am the preloading these urls in the success portion of my $.ajax request and want to call a function to do something with these images once they have totally loaded. I am trying to do this by using the $.Deferred object in the complete portion of my $.ajax request, and resolving the $.Deferred at the end of my image preloading loop.
However, as I monitor the console, I can see that the $.ajax calls continue to be made while the images are preloading. the $.Deferred is not resolved until the end of the preloading loop, so I do not understand why this would happen. I have a limited understand of the $.Deferred but am trying to learn more so any advice on why this is happening and how to fix this would be great. The understanding I got was that they were a way to prevent a function from being called until they are reserved...
As a bonus here, other than ugly code is there an issue with making multiple $.ajax requests and doing the same thing with the data that is returned? Thanks
$(function(){
(function poll(){
var streamUrl = 'http://demofeed';
var def = $.Deferred();
console.log('about to get');
setTimeout(function(){
$.ajax({
url: streamUrl,
dataType: 'json',
success: function(data) {
createArray(data, def);
},
complete: function () {
$.when(def).done(function() {
//Long-poll again
poll();
});
},
error: function(){
setTimeout(function(){
console.log('There was an error with the XHR Request');
poll();
}, 3000);
}
});
}, 7000);
})();
});
function createArray(data, defer){
console.log('creating array');
var entries = data['entries'];
for (var k = 0, j = entries.length; k < j; k++) {
console.log('there is new media');
if (entries[k].img) {
var img = new Image();
img.onload = (function(entry) {
return function() {
validArray.push(entry);
console.log('New Media Loaded');
}
})(entries[k]);
img.onerror = function(){
console.log('error: bad image source');
};
img.src = entries[k].url;
} else {
console.log('Not an image');
}
}
defer.resolve(); //Here is where I resolve the deferred object, once for loop is done (all images have been loaded)
}
$.when waits for promises.
You are passing it a deferred object instead. It in turn gets converted to a promise by wrapping it, so waiting for it yields immediately with it as the value. So the .when executes immediately. You can instead use def.promise() to get a promise for it.
That said, I'd probably do a more promisified API overall if I were you.
var delay = function(ms){
var def = $.Deferred();
setTimeout(function(){ def.resolve(); },ms);
return def.promise();
}
Which would let me do:
function poll(){
console.log('about to get');
return delay(7000).then(function(){
return $.get('http://demofeed');
}).then(function(){ // success case
return createArray(data); // we'll get to it later
}), function(){ // handle error
console.log('There was an error with the XHR Request');
return delay(3000); // wait 3 ms
}).then(poll); // long poll
}
Look how clear it is and hot it only has two levels of indentation, this is because promises chain. If your code was sequential, this is roughly:
function poll(){
sleep(7000);
try{
var data = $.get('http://demofeed'); // assume this is sync
arr = createArray(data);
} catch(e) {
console.log("There was an error with the XHR Request");
sleep(3000);
}
poll(); /// call again
}
As for the load images function, consider using $.when.apply on it to have $.when wait for multiple values:
function createImages(data){ // what does this return?
var promises = data.entries.map(function(entry){
var img = new Image();
var d = $.Deferred();
img.onload = function(entry){ d.resolve(entry); };
img.onerror = function(){ console.log("Bad Image usage"); }
return d.promise();
});
// this will actually _wait_ for the images now.
return $.when.apply($, promises);
}
Last note, if I were you I'd avoid jQuery promises and use a more capable library like Bluebird.

Categories

Resources