Angular's implementation of deferred only displays the final value in the loop

I've created a personalized synchronization process that queues up all my sync records in sequence. When my service retrieves multiple sync records, it processes them and updates the last sync date for successful records, or logs errors for failed records (without updating the last sync date) and stops the sync process.

To achieve this, I have utilized $q.all from AngularJS. Here's an excerpt from the sync loop:

    var processes = [];

    for (var i in data) {
        // Checking for valid data fields
        if (data[i] === null || data[i].TableName == null || data[i].Query == null || data[i].Params == null) {
            throw new TypeError("ERROR! Unexpected data retrieved from the download sync process.");
        }

        // Processing parameters
        var params = data[i].Params;
        var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
        var paramsArray = paramsMassaged.split(",");

        mlog.Log("Query: " + data[i].Query);
        mlog.Log("Params: " + paramsArray);

        // Handling different table cases
        if (data[i].TableName === "table1" || data[i].TableName === "table2") {
            var process = $DBContext.ExecuteSyncItem(data[i].Query, paramsArray);

            process.then(
                function () {
                    $DBConfigurations_DBContext.UpdateLastSyncDate(data[i].CreatedDate, function (response) {
                        mlog.Log(response);
                    });
                },
                function (response) {
                    mlog.LogSync("Error syncing record: " + response, "ERROR", data[i].Id);
                },
                null
            );

            processes.push(process);
        } else {
           mlog.LogSync("WARNING! Excluded table from sync process. Outdated application version. Table: " + data[i].TableName);
        }
    }

    // Running all processes concurrently using $q.all
    $q.all(processes)
        .then(function (result) {
            mlog.LogSync("---Finished syncing all records");
        }, function (response) {
            mlog.LogSync("Sync Failure - " + response, "ERROR");
        });

For instance, consider the ExecuteSyncItem function below:

ExecuteSyncItem: function (script, params) {
    window.logger.logIt("In the ExecuteSyncItem function...");

    var primaryKey = params[params.length - 1];

    var deferred = $q.defer();

    $DBService.ExecuteQuery(script, params,
        function (insertId, rowsAffected, rows) {
            window.logger.logIt("rowsAffected: " + rowsAffected.rowsAffected);

            if (rowsAffected.rowsAffected <= 1) {
                deferred.resolve();
            } else {
                deferred.resolve(errorMessage);
            }
        },
        function (tx, error) {
            deferred.reject("Failed to sync record with primary key: " + primaryKey + "; Error: " + error.message);
        }
    );

    return deferred.promise;
}

The issue arises when multiple sync records fail, causing the same error message to be displayed for each failure. How can I ensure that specific information is shown for each failing record instead of repeating the same message?

Answer №1

As suggested by comradburk, one way to address this issue is by encapsulating your processes in a closure within a loop. However, there is also an alternative approach in Angular for solving this problem. Instead of using the traditional for-in loop, you can utilize angular.forEach() to iterate through all the data elements.

var processes = [];

angular.forEach(data, function(item) {
    if (item === null || item.TableName == null || item.Query == null || item.Params == null) {
        // Error handling logic...
        throw new TypeError("ERROR! The data retrieved from the download sync process was unexpected.");
    }

    var params = item.Params;
    var paramsMassaged = params.replaceAll("[", "").replaceAll("]", "").replaceAll(", ", ",").replaceAll("'", "");
    var paramsArray = paramsMassaged.split(",");

    mlog.Log("Query: " + item.Query);
    mlog.Log("Params: " + paramsArray);

    if (item.TableName === "table1") {
        var process = $table1_DBContext.ExecuteSyncItem(item.Query, paramsArray);

        process.then(
            function () {
                $DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
                    mlog.Log(response);
                });
            },
            function (response) {
                mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
            },
            null
        );

        processes.push(process);
    } else if (item.TableName === "table2") {
        var process = $table2_DBContext.ExecuteSyncItem(item.Query, paramsArray);

        process.then(
            function () {
                $DBConfigurations_DBContext.UpdateLastSyncDate(item.CreatedDate, function (response) {
                    mlog.Log(response);
                });
            },
            function (response) {
                mlog.LogSync("Error syncing record: " + response, "ERROR", item.Id);
            },
            null
        );

        processes.push(process);
    } else {
        mlog.LogSync("WARNING! This table is not included in the sync process. You have an outdated version of the application. Table: " + item.TableName);
    }
});

$q.all(processes)
    .then(function (result) {
        mlog.LogSync("---Finished syncing all records");
    }, function (response) {
        mlog.LogSync("Sync Failure - " + response, "ERROR");
    });

Answer №2

The issue arises from the closure formed on the variable i. When the callback function is executed, i retains the last value from the for loop. To avoid this, you should bind the value of i to a separate and unchanging variable. One way to achieve this is by using a self-invoking function.

for (var i in data) {
    (function(item) {
        // Insert your logic here and replace i with item, for instance
        mlog.LogSync("Error syncing record: " + response, "ERROR", data[item].Id);
    })(i);
}

For more information on why closures result in this issue, you can check out: Javascript infamous Loop issue?

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

The use of jQuery addClass does not produce any changes

I am trying to modify a single attribute in my class using a JavaScript function. .msg_archivedropdown:before { content:""; display: block; position:absolute; width:0; height:0; left:-7px; top:0px; border-top: 10px solid tr ...

Flask Socket-IO - Instant Messaging App with Real-Time Communication, Socket Functionality Not Operating as Expected

I've been dedicating a good amount of time to this flask project, working on incorporating Flask-SocketIO along with JavaScript SocketIO to enable real-time messaging capabilities. Here's the Python section of the code: ## Setting up Socket IO f ...

Click to start viewing the video

I'm attempting to have a video play when clicked, either on the video itself or around the video. There are multiple videos on the page and I've tried various solutions including the script below which didn't work. $('video'). ...

create an HTML element using JavaScript to display a box with dimensions of n

I am attempting to create a grid in an HTML document using only plain JavaScript. The idea is to take a number from a URL and use that as the basis for generating the grid. For example, if my URL looks like this: abc.html?num=5, then I would need to creat ...

Troubleshooting a problem with AJAX returning the data

Currently, I have a javascript function that calls another javascript function called zConvertEmplidtoRowid. This second function utilizes an ajax call to execute a query and retrieve data stored in a variable named rowid. My challenge lies in figuring out ...

Singleton constructor running repeatedly in NextJS 13 middleware

I'm encountering an issue with a simple singleton called Paths: export default class Paths { private static _instance: Paths; private constructor() { console.log('paths constructor'); } public static get Instance() { consol ...

Having trouble with accessing an element that contains both onclick and text attributes in Selenium Webdriver?

The HTML code I'm dealing with includes this element: <a style="text-decoration:none; font-weight:normal;" href="javascript:void(0);" onclick="CreateNewServiceItemApproved();"> <img src="icons/ui/addnew.png"> <span style="color:# ...

The system is unable to locate a compatible object with the identifier '[object Object]' of type 'object'. NgFor is limited to binding with iterables like Arrays, not JSON data

Working with JSON data data:[ { assets:[[tool_order_id: "38",order_status_id: "10"]], order_info:[id: "1", order_type: "6",check: "1", current_Stage_id: "29"] }, { assets:[tool_order_ ...

Error: The XML parsing in ASP failed to find a root element at the specified location

When clicking the button, I have jQuery/Ajax code that is supposed to pass the value of a selected radio button to a controller action and open a detail page. However, I am encountering an error. When using Mozilla Firefox, the console displays: XML Par ...

What flaws are present in this authentication system?

As a developer with a passion for coding, rather than a security expert, I came across The definitive guide to form-based website authentication, which highlighted the importance of SSL or complex algorithms in safeguarding login data from eavesdropping. D ...

React - triggering a router link action before the link is assigned a value

In this scenario, the issue arises because the URL changes before the link receives a value. The state is being received directly through the component but by the time it happens, the component has already been clicked. This results in the value being as ...

Implement Cross-Origin Resource Sharing using Hazelcast

Right now, I have integrated Hazelcast to create a RestAPI for an Angular Client. Is it possible to enable CORS on our Hazelcast server? I haven't been able to locate any information regarding this in their documentation. ...

What's causing the error "is not a function" to appear?

I am currently facing an issue while attempting to develop an angular service. Here is the code snippet for the service: var app = angular.module('plunker', []); // Filter Service that returns records with crdamt positive: app.factory('Fil ...

How to Identify CanActivate in Angular 2 Deprecated Routing?

One issue I am facing involves a component that has been decorated with @CanActivate. @Component({ // ... }) @CanActivate(() => false) export class UserManagementComponent { // ... } My dilemma lies in the fact that I want to disable or hide t ...

Dynamically loading components within an Angular application

I am tasked with displaying different components at specific times by iterating through them. Below is an example of how I have attempted to achieve this. The components I can use are determined by the server. <ngb-tabset [activeId]="1"> ...

Iterating over an array while postponing a function

My goal is to create a continuous loop through an array of number values. These values will be used as delay parameters in a setInterval function, triggering another function each time. Here's what I've come up with: HTML: <p>On</p> ...

Top method for triggering an action on the client-side during Sign In with the help of Redux, React, and NextAuth

Currently, I am developing a web application that utilizes the Spotify API. My goal is to seamlessly load the user's playlists as soon as they log in using NextAuth. At the moment, there is a button implemented to trigger playlist loading, but it onl ...

Load CSS stylesheet depending on Internet Explorer document mode

As I work on my website, I am facing the challenge of incorporating different versions of my style sheet based on the browser's document mode, not the browser mode. For instance, if the documentmode = ie8, I may need to load main_ie8.css, whereas for ...

extract the key identifier from the JSON reply

My JSON ResponseData example for form0 is provided below: { "MaterialType": "camera", "AssetID": 202773, "forms": [ { "release": "asyncCmd/accessCameraMulti", "action": "rest/Asset/202773/cameraAccessMultiple", ...

A child component in Vue.js unexpectedly starts updating when the parent component renders and uses object literals as props

I'm currently facing an issue with my Vue components where object literals are passed as props like: <child :prop1="{ foo: 'bar' }"></child> After rerendering the parent component, the prop prop1 changes and triggers an update ...