Fulfill the promise once all images have finished loading

When I was preloading images, my initial code looked like this:

    function preLoad() {
        var deferred = $q.defer();
        var imageArray = [];
        for (var i = 0; i < $scope.abbreviations.length; i++) {
            imageArray[i] = new Image();
            imageArray[i].src = $scope.abbreviations[i].imgPath;
        }
        imageArray.forEach.onload = function () {
            deferred.resolve();
            console.log('Resolved');
        }
        imageArray.forEach.onerror = function () {
            deferred.reject();
            console.log('Rejected')
        }
        return deferred.promise;
    }
    preLoad();

Initially, I thought the images were all loading correctly because I saw the 'Resolved' log.

However, someone later pointed out that only the first promise is resolved in the above code and it doesn't guarantee that all images are loaded before resolving the promise.

I was recommended to use $q.all applied to an array of promises instead, leading to the following code:

    function preLoad() {
        var imageArray = [];
        var promises;
        for (var i = 0; i < $scope.abbreviations.length; i++) {
            imageArray[i] = new Image();
            imageArray[i].src = $scope.abbreviations[i].imgPath;
        };

        function resolvePromises(n) {
            return $q.when(n);
        }
        promises = imageArray.map(resolvePromises);
        $q.all(promises).then(function (results) {
            console.log('array promises resolved with', results);
        });
    }
    preLoad();

The updated code works, but I'm still trying to understand:

  1. what each function does;
  2. why using $q.all ensures that all images are loaded before resolving the promises.

The related documentation can be a bit difficult to comprehend.

Answer №1

Take a look at this plunkr example.

This is the function you can use:

function preLoad() {

    var promises = [];

    function loadImage(src) {
        return $q(function(resolve,reject) {
            var image = new Image();
            image.src = src;
            image.onload = function() {
              console.log("loaded image: "+src);
              resolve(image);
            };
            image.onerror = function(e) {
              reject(e);
            };
        })
    }

    $scope.images.forEach(function(src) {
      promises.push(loadImage(src));
    })

    return $q.all(promises).then(function(results) {
        console.log('promises array all resolved');
        $scope.results = results;
        return results;
    });
}

The concept here is quite similar to Henrique's answer, but in this case, the onload handler resolves each promise, while onerror rejects it.

To address your queries:

1) Promise factory

$q(function(resolve,reject) { ... })  

creates a Promise. Whatever is passed to the resolve function will be used in the then function. For instance:

$q(function(resolve,reject) {
     if (Math.floor(Math.random() * 10) > 4) {
         resolve("success")
     }
     else {
         reject("failure")
     }
}.then(function wasResolved(result) {
    console.log(result) // "success"
}, function wasRejected(error) {
    console.log(error) // "failure"
})

2) $q.all receives an array of promises, and the then function takes another function which gets an array with the resolutions of all original promises.

Answer №2

I am not very familiar with the angular promise library, but here is a basic outline of how it works:

function fetchImage(imgData) {
    var imageElement = new Image();
    imageElement.src = imgData.imgPath;

    return $q(function(resolve, reject){
        imageElement.addEventListener('load', function(){
            if ((
                   'naturalHeight' in this 
                    && this.naturalHeight + this.naturalWidth === 0
                ) 
                || (this.width + this.height == 0)) {
                reject(new Error('Image failed to load:' + this.src));
            } else {
                resolve(this);
            }
        });

        imageElement.addEventListener('error', function(){
            reject(new Error('Image failed to load:' + this.src));
        });
    })
}

function preLoadImages() {
    return $q.all($scope.imageURLs.map(fetchImage));
}

// implementation example
preLoadImages().then(function(data){
    console.log("Images loaded successfully");
    data.map(console.log, console);
}, function(reason){
    console.error("Error loading images: " + reason);
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble with npm debounce in ReactJS?

When using the npm debounce package in ReactJS, I encountered an error with the code below: Javascript - Uncaught TypeError: Object(...) is not a function The error occurs when passing the function into the debounce() method. import React, { Component ...

Is the zero-height navigation bar still apparent on the screen?

I am currently working on a portfolio website and I have a specific idea in mind for how I want the work to be displayed. I would like it to slide up onto the screen when the user clicks on the portfolio section, similar to how a navigation bar slides over ...

Show popup window during servlet processing

I'm attempting to display a modal box in a Java servlet with the message "Please wait while your request is processed". I understand that I can make an AJAX call and manage the modal in the front-end code. Currently, I trigger the servlet when the us ...

Issue with initialization of select control when utilizing sub object

I'm having trouble initializing a dropdown menu filled with objects to match the current value. Dropdown menu in HTML <div class="form-group"> <label class="control-label" for="stat">Stat</label> ...

When using express and passport-local, the function `req.isAuthenticated()` will typically return a

I'm seeking some insight into my current situation. I've noticed that whenever I call req.isAuthenticated() in an app.router endpoint, running on port 3001 via the fetch API, it always returns false. It seems like the connect.sid is not being pro ...

Experiencing difficulty fetching JSON data from a remote URL due to an error

I'm attempting to load a remote JSON file from this link. Initially, I used the $http.get function, but encountered the following error message: CORS 'Access-Control-Allow-Origin' Switching to JSONP didn't solve the issue either. ...

What is the best way to scale down my entire webpage to 65%?

Everything looks great on my 1920x1080 monitor, but when I switch to a 1024x768 monitor, the content on my webpage becomes too large. I've been manually resizing with CTRL+Scroll to reduce it to 65%, which works fine. Is there a code solution using CS ...

The CSS navigation bar is not properly aligned in the center

This menu was constructed by me: JSBIN EDIT ; JSBIN DEMO Upon closer inspection, it appears that the menu is not centered in the middle of the bar; rather, it is centered higher up. My goal is to have it positioned lower, right in the middle. I ...

What is the best method for generating a vertical tab using JavaScript in a paragraph format?

Struggling to create a menu similar to this using bootstrap 4. Despite my efforts, I keep encountering various issues. Is there someone who can help me solve this problem? Remember, I am using bootstrap 4. Here is a demo: <!doctype html> <html ...

Deactivate a form on the page while another form is being used

My issue involves having two forms on the same web page with identical IDs, and I'm unable to easily change them. Both forms are necessary on the page. When I submit one form, it also submits the other form as blank, resulting in duplicate submission ...

Can you explain the process of utilizing a JavaScript function that has been fetched via Ajax

When the AJAX function is loaded on the page, I am attempting to execute another function. The code snippet for the AJAX call: $.ajax({ type: "POST", url: "loginpersonal.asp", data: "id=<%=request("id")%>", beforeSend: function() { $("#personal ...

eliminating the hues beneath the lines on Morris region charts

I'm seeking advice on how to remove colors below the lines in Morris area charts. Any ideas? Here's the code snippet I've been using: Morris.Area({ element: 'area-example', data: [ { y: '2006', a: 100, b: 90 }, ...

Once data is passed from ajax to php, the page may experience loading delays upon the initial entry, but subsequent entries will load normally

I'm working on my webpage and I want to display a list of items fetched from the server. Each item will have a "Complete" button underneath it. When this button is clicked, the item's ID will be sent to a PHP file. Here is the code for my form: ...

What are the steps to ensure compatibility with relative paths when transitioning from v5 to v6?

In my application, there are scenarios where multiple routes must pass through a component before rendering specifics. Additionally, there are situations where something is displayed for the parent route and then divided for the children. It's crucia ...

How can I ensure that remote_form_for adds new code in addition to existing code rather than replacing it in RoR?

I'm currently utilizing remote_form_for within Ruby on Rails to generate a form that will inject some HTML into the current page upon submission. However, my goal is to allow users to utilize this pop-up for inserting HTML multiple times, as opposed t ...

Leveraging mongo aggregate functionality within the meteor framework to calculate the total number of unlocked and locked

So, I have a collection where I store documents related to users with a structure like: {_id: "hofjew233332j4", userId: "fhewojfw34324", achievementUnlocked: true }; My goal is to use the aggregate and underscore functions to group the documents by user ...

Error in Mocha test: Import statement can only be used inside a module

I'm unsure if this issue is related to a TypeScript setting that needs adjustment or something else entirely. I have already reviewed the following resources, but they did not provide a solution for me: Mocha + TypeScript: Cannot use import statement ...

Discovering escape characters while iterating through a string in javascript

I have a situation where I must rearrange a string into an array for a unique user display. Whenever encountering an escape character (such as \n for a new line), it needs to be added as a separate item in the array. For example, if the string is: sa ...

React's setInterval acting unpredictably

Creating a stopwatch functionality in React was my recent project. To achieve updating the number every second, I implemented setInterval as follows: import React, {useEffect, useState, useRef} from 'react'; const MAX_TIMER = 5; export defa ...

What is the best approach for managing client-side routes when a page refresh displays the backend Rails route instead?

After deploying my SPA to Render, I encountered an issue. My application comprises a React frontend with Redux and a Rails backend. When the app initializes, it correctly displays the frontend route in the browser. Subsequent navigation without refreshing ...