How to load images with chained Promises - javascript

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

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 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.

Bug in jQuery Deferred Code

The following code has a bug that the ....promise.then(result) is executing without waiting for the file read to complete:
doAsyncReadFromDisk = function(myReader)
{
var deferred = $.Deferred();
myReader.onload = function(event) {
deferred.resolve(event.target.result);
};
myReader.onerror = function() {
deferred.reject(this);
};
var rgReceivedFiles = document.getElementById('gdeReadFromDisk');
console.log('Selected file: ' + rgReceivedFiles.files.item(0).name);
myReader.readAsText(rgReceivedFiles.files.item(0));
return deferred.promise();
} // doAsyncReadFromDisk()
catalogFileParseCB = function(){
var fileReader = new FileReader();
var myPromise = doAsyncReadFromDisk(fileReader);
//alert('Space holder to let file read finish!');
myPromise.then(console.log(fileReader.result));
} // catalogFileParseCB
When the
alert('Space holder to let file read finish!');
line is uncommented, the file contents are properly available. So the conclusion is that the myPromise.then(...) call is executing without waiting for the resolution of the deferred call.
This fails on files ~200MB (real) data as well as smaller files (test data). Not able to locate the issue, I am inclined to believe that the issue is not
Wondering if someone can help identify the bug. Is there a better way of doing this? We are guaranteed to read only one file at a time, but should scale data size of several GB.
Thanks for your time, appreciate etc.
The argument to .then() is a function to call when the promise is resolved.
myPromise.then(function() {
console.log(fileReader.result);
});

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.

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