Managing individual promises within an array efficiently

I am facing an issue with my function that calls web services on my server and returns an array of promises.

The problem is that if one of the calls fails, the entire process is marked as a failure. For example, out of 5 calls, 1 might fail, causing the whole function to fail. I need a way to properly log this information but I'm unsure how to proceed.

An ideal log would look like:

  1. call 1 passed
  2. call 2 passed
  3. call 3 passed
  4. call 4 failed - reason
  5. call 5 passed

Currently, the function returns "The handle user operation failed" when call 4 fails.

Function:

var manageGroup = function (add, group, users){

    var deferred = $q.defer();
    var arrPromises = [];
    var promiseIndex = arrPromises.length;
    var usersLength = users.length;
    var operation = add ? "AddUserToGroup" : "RemoveUserFromGroup";
    var actionText = add ? "Added: " : "Removed: ";
    var actionText2 = add ? " to " : " from ";

    //Apply operation on selected groups
    for (var i = 0; i < usersLength; i++){
        arrPromises[i] = $().SPServices({
            operation: operation,
            groupName: group.name,
            userLoginName: users[i].domain
        });      
    }

    $q.all(arrPromises).then(
        function (){
            //when promises are finished
            for (var i = 0; i < usersLength; i++){
                console.log(actionText + users[i].name + actionText2  + group.name);
            };
            deferred.resolve();
        },
        //function in case of AJAX failure
        function (){
            alert('The handle user operation failed.');
        }
    ) 
    return deferred.promise;      
}

I attempted to handle promises individually instead of using $q.all, but now nothing is being logged:

I removed this section:

/*$q.all(arrPromises).then(
    function (){
        //when promises are finished
        for (var i = 0; i < usersLength; i++){
            console.log(actionText + users[i].name + actionText2  + group.name);
        };
        deferred.resolve();
    },
    //function in case of AJAX failure
    function (){
        alert('The handle user operation failed.');
    }
) */

And introduced this instead:

for (var i = 0; i<promiseIndex; i++){
    arrPromises[i].then(
        function (){
            console.log(actionText + user[i].name + actionText2 + group.name);
        }
    ),
    function (){
        alert('Failed to add/remove'+  user[i].name + ' to ' + group.name)
    }
}

$q.all(arrPromises).then(function (){
    deferred.resolve();
}, function (){
    deferred.reject();
})

Answer №1

Q (the foundation for ng.$q) and bluebird offer solutions that perfectly align with your requirements.

If you opt for bluebird, here's the approach to take:

var Promise = require('bluebird');

Promise.settle(arrPromises).then(function(promises) {
    promises.forEach(function(promise) {
        if (promise.isRejected()) {
            // Handle a rejected promise.
        }
        else {
            // Handle a resolved promise.
        }
    });
});

Alternatively, for Q, follow these steps:

var Q = require('q');

Q.allSettled(arrPromises).then(function(promises) {
    promises.forEach(function(p) {
        if (p.state === 'fulfilled') {
            // Handle a resolved promise.
        }
        else {
            // Handle a rejected promise.
        }
    });
});

An added benefit of these libraries is their adherence to the Promises/A+ specification. This means you can seamlessly replace ng.$q with either of these options without disrupting your existing code functionality.

Answer №2

If you are facing an issue where one ajax call failure leads to the overall failure of all(), there is a workaround. You can catch the failure of individual AJAX calls and resolve the corresponding promise with a value of your choice. In this example, an empty string is used as the fallback value.

Check out the live demo (click here).

Please note that this code snippet is for demonstration purposes only.

  // Store promises to be used in all()
  var promises = [];

  // Loop for making ajax calls
  for (var i=0; i<3; ++i) {
    // Create a new deferred object for each call
    var deferred = $q.defer();
    // Cache the promise
    promises[i] = deferred.promise;
    // Use a separate function to handle the call and avoid unwanted variable increment
    makeCall(deferred, i);
  }

  $q.all(promises).then(function(allData) {
    console.log(allData);
  });

  function makeCall(deferred, i) {
    // Make the ajax call
    $http.get('file'+i+'.txt').then(function(resp) {
      console.log('Call '+i+' returned.');
        // Resolve the promise with ajax data if successful
        deferred.resolve(resp.data);
    }, function() {
      // Resolve with a different value on failure
      deferred.resolve('');
    });
  }

Answer №3

I attempted to manage the promises individually instead of utilizing $q.all, but now nothing is appearing in the log

It appears you've encountered the age-old closure-in-a-loop issue, where your i variable holds an incorrect value at the time the callback is executed. To address this, try the following approach:

for (var i = 0; i<promiseIndex; i++) (function(i) {
    arrPromises[i].then(function() {
        console.log(actionText + user[i].name + actionText2 + group.name);
    }, function( ){
        alert('Failed to add/remove'+  user[i].name + ' to ' + group.name)
    });
})(i);

You also had a problem with mismatched parentheses in the then call, causing the error handler not to be passed.

Now each promise is dealt with separately, although their order isn't preserved. To maintain order, consider using some form of all, as suggested by @Florian's answer.


Additionally, there's no need to explicitly use that deffered. Simply utilize return $q.all(arrPromises)! Manually resolving deferreds and returning their promise can be cumbersome and prone to errors - as seen in your original code where you forgot to reject it in case of an error. Avoid this when you already have promises and can employ combinators on them.

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

Experience the combined power of addthis, isotope, and nicescroll - all in a single

I am utilizing a WordPress template that includes a set of share buttons from AddThis. <ul class="addthis extra"> <li class="addthis-hold"> <div class="addthis_toolbox" addthis:url="<?php the_permalink( ...

What could be causing my nested child component to fail to display the content?

My current setup includes: Vue 2.5.16 Veux Vue router I have created a simple router view that searches for a child component within a parent component using the URL structure: /folders/parent-uuid/child-uuid Below is the code for the parent component ...

Encountering a 500 error while attempting to send data to an API route within a Laravel web page using XMLHttpRequest at http://127:8000/api/v1/exemp. Can anyone

let requestData = { products: [ { description: "product1", barcode: "123456", price: 10, note: "note1" }, { description: "product2", barcode: "654321", price: 20, note: "note2" ...

Issues arise when attempting to bind an AngularJS directive template with OpenLayers

My custom directive focuses on developing an openlayers map application using Angular. <div ng-app="app"> <map-container></map-container> </div> If you want to check out the working code, click here: angular.module("app",[]); ...

Converting the 'require' call to an import may be a more efficient method when importing package.json in a typescript file

In my current project, I am creating a class where I am directly accessing the package version number like this: const pkg = require('../package.json') export class MyClass() { constructor() { // Set the base version from package.jso ...

The show-word-limit feature is malfunctioning on the element.eleme.io platform

After attempting to utilize the show-word-limit attribute of the input element, unfortunately the character count did not display. Below is the code snippet that was used: <el-input type="textarea" placeholder="Please input" ...

What is the best way to remove multiple rows from a table using Angular.js?

I'm attempting to tackle the task of deleting multiple rows from a table using checkboxes in Angular and PHP. Unfortunately, I've hit a roadblock and my understanding of Angular seems to have reached its limit! Let's take a look at my table ...

What is the process for incorporating an animated gif into a scene?

I'm trying to incorporate an animated gif in three.js. What's the best way to do it? var materialTextured = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture('images/pin.gif'), transparent: true, ...

Inconsistent performance of AJAX functionality

I've been diving deep into this problem for quite some time now. I have a feeling that someone with the right expertise will be able to pinpoint the issue, but for some reason, it's just not clicking for me. The basic functionality here revolves ...

What is a way to test short javascript code without using a web browser?

I have taken on the challenge of teaching beginners how to code in JavaScript. My approach involves giving them snippets of code with intentional errors for them to fix: const square = function(x) { return x + x; // Wrong! This will double the number, ...

Unable to integrate Express.js into my React JS project

Having trouble integrating express.js into my react js Project. After adding the following lines to my app.js code: const express = require('express') const app = express(); I encounter the error messages below: - Module not found: Error: Can&ap ...

Contrasting results when logging an element in Chrome versus IE

Running the script below in Internet Explorer gives the expected output for console.log(target_el): <div class="hidden"></div> However, when run in Chrome, the output changes to: <div class="visible"></div> To add a humorous twi ...

mapping buttons to images based on dynamic conditions

In my folder, I have over 1000 files. Using opendir, readdir, and is_dir, I can display the thumbnails of these files in my browser. Each thumbnail also has a corresponding button assigned to it. What I'm looking to do now is delete the 500th image wh ...

When using the `Node fs.readstream()` function, the output includes `<Buffer 3c 3f 78 6d 6c ...>`, which is not in a readable

Handling a large XML file (~1.5gb) in Node Js by streaming it to process data in chunks has proven challenging due to the complexity of the documentation. A simple code snippet I am currently using is: var fs = require('fs'); var stream = fs.c ...

Automatically adjust height directive to fill up the remaining space

Is there a way to dynamically adjust the height of an element to fill the remaining space within its container? In my current layout, I have a fixed-height header (or menu) in pixels, a content area that may overflow the window height and require scroll b ...

Images mysteriously vanishing after importing the Bootstrap stylesheet

One of the features on my website is a page where, upon hovering over an image, an overlay appears with a caption. Below is the code for one such image: <div id="content"> <div class="mosaic-block fade"> <a targe ...

Steps to transfer extra files such as config/config.yaml to the .next directory

I have the following folder structure for my NextJS project: _posts/ components/ hooks/ config/ <--- includes config.yaml file for the server pages/ public/ .env.local ... yarn build successfully copies all dependencies except for the config/ folder. ...

add an svg string element to a pre-existing svg element

I already have an <svg></svg> element in my code and now I want to add another svg element to it as a string. For instance: var new_svg = '<g><text x="100" y="100">Hello</text></g>'; d3.select('svg' ...

What is the best way to retrieve the default selected value in JavaScript and send it to PHP when the page is loaded?

Currently, I am facing an issue where I can only display the value as an output after manually selecting a Grade from the dropdown list on the page. However, I am looking to have the default selected Grade value displayed as an output when the page is init ...

What is the best way to pass a JavaScript variable to an Ajax script?

Within an html button, I have the following onclick event: onclick='javascript:updateStatus(59)' This event calls the function below: function updateStatus(){ $.ajax({ type: 'post', url: '/update-status.php', ...