Effective Angular - ensuring all API calls are completed in a forEach loop before returning the final array

Struggling with the asynchronous nature of Angular, I'm faced with a challenge. My task involves looping through various cards where certain types require API calls while others do not. However, upon completion of the loop, only the cards that do not need API calls are being returned.

To illustrate how it's currently functioning:

// If color of colorCard is blue, it requires 2 API calls
// If color of colorCard is red, it requires 1 API call
// If color of colorCard is yellow, no API call needed
// For this example, let's say there's one yellow card, one blue card, and two red cards in colorCards

var buildCards = function() {
  var finishedArray = [];
  var promises = [];
  colorCards.forEach(function(e, i){
    if (e.color === 'blue') {
      promises.push(firstBlueApiCall); 
      var firstBlueIdx = 0;
      promises.push(secondBlueApiCall); 
      var secondBlueIdx = 1;
    } else if (e.color === 'red') {
      promises.push(redApiCall); 
      var redIdx = 0;
    }

    // Only push to finishedArray after completing API calls for blue or red cards
    if (promises.length > 0) {
        $q.all(promises).then(function(response) {
          if (e.color === 'blue') {
            e.firstBlueData = response[firstBlueIdx];
            e.secondBlueData = response[secondBlueIdx];
          } else if (e.color === 'red') {
            e.redData = response[redIdx];
          }
          finishedArray.push(e);
          promises = [];
        });
    // For yellow cards, simply push to finishedArray without any API calls
    } else {
      finishedArray.push(e);
      promises = [];
    }
  })
  return finishedArray;
}

The issue arises when the 'return finishedArray' statement only includes the yellow card without waiting for the red/blue cards to finish their API calls. How can I make sure all four cards are included in the final array?

Answer №1

The streamlineCards function has been streamlined:

var streamlineCards = function(colorCards) {
  var promises = [];
  colorCards.forEach(function(card, i){
    promises.push(preparePromise(card));
  });
  return $q.all(promises);
}

Creating a promise with $q.defer() is unnecessary since $q.all() already returns a promise. Moreover, a custom-made promise does not handle rejection properly. If any of the promises in $q.all() are rejected, the manually created promise using $q.defer() will not resolve.

This practice is referred to as a Deferred Anti-Pattern and should be avoided.

Similarly, adjustments can be made to the preparePromise function to eliminate the Deferred Anti-Pattern:

var preparePromise = function(card){
  var localPromises = [];

  if (card.color === 'blue') {
    localPromises.push(blueRequest1); var firstBlueIdx = promises.length - 1;
    localPromises.push(blueRequest2); var secondBlueIdx = promises.length - 1;
  } else if (card.color === 'red') {
    localPromises.push(redRequest); var redIdx = promises.length - 1;
  }

  var cardPromise;
  if (localPromises.length > 0) {
      cardPromise = $q.all(localPromises).then(function(res) {
        if (card.color === 'blue') {
          card.firstBlueData = res[firstBlueIdx];
          card.secondBlueData = res[secondBlueIdx];
        } else if (card.color === 'red') {
          card.redData = res[redIdx];
        }
        return card;
      });
  } else {
      cardPromise = $q.when(card);
  }
  return cardPromise;
}

The then method of a promise always results in a new promise that resolves with the value returned by the handler function. This approach also handles rejections correctly by passing them down the chain if the original promise is rejected, preventing issues like those seen with $q.defer().

Additionally, when there are no promises for processing with $q.all(), utilizing $q.when() can create the cardPromise.

By chaining promises through the .then method, a series of derived promises can be generated easily. The ability to resolve a promise with another promise allows deferring resolution along the chain, enabling the creation of powerful APIs.

-- AngularJS $q Service API Reference - Chaining Promises

Always opt for promise chaining over employing a Deferred Anti-Pattern.

Answer №2

After facing this challenge, here is the solution I came up with:

const handlePromise = function(data){
  let deferredData = $q.defer();
  let localPromisesList = [];

  if (data.color === 'blue') {
    localPromisesList.push(blueApiCall1); const firstBlueIndex = promises.length - 1;
    localPromisesList.push(blueApiCall2); const secondBlueIndex = promises.length - 1;
  } else if (data.color === 'red') {
    localPromisesList.push(redApiCall); const redIndex = promises.length - 1;
  }

  if (localPromisesList.length > 0) {
      $q.all(promises).then(function(response) {
        if (data.color === 'blue') {
          data.firstBlueInfo= response[firstBlueIndex];
          data.secondBlueInfo= response[secondBlueIndex];
        } else if (data.color=== 'red') {
          data.redInfo= response[redIndex];
        }
        deferredData.resolve(data);
      });
  } else {
    deferredData.resolve(data);
  }
  return deferredData.promise;
}

const generateAllCards = function() {
  const deferred = $q.defer();
  let finalCardArray = [];
  let allPromises = [];
  colorCollection.forEach(function(colorData, index){
    allPromises.push(handlePromise(colorData));
  });
  $q.all(allPromises).then(function(cards) {
    deferred.resolve(cards)
  })
  return deferred.promise;
}

I made sure to utilize promises throughout the process and it ended up being successful. Hopefully, this can assist others facing similar challenges in the future.

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

What benefits does registering a Vue component locally provide?

According to the Vue documentation, global registration may not always be the best option. The documentation states: Global registration is not always ideal. When using a build system like Webpack, globally registering all components can result in unnece ...

"Transmit the document by utilizing the file ID in the form of

When sending a file from my server, I can easily define the path and it goes through successfully. However, with the node-telegram-bot-api, there is an option to send a document that is already hosted on telegram servers by providing the file_id in the doc ...

A tale of twp distinct HTML experiences with AngularJS

In my current scenario, I have a JSON file containing all the necessary data. This data is used to generate an HTML component in my code. However, there is a challenge where the component code occasionally needs to change: specifically, a <div> must ...

Error occurs when attempting to access window.google in Next.js due to a TypeError

I've been working on integrating the Google Sign In feature into my Next app. Here's how I approached it. In _document.js import React from 'react'; import Document, {Html, Head, Main, NextScript } from 'next/document'; expo ...

Showing a group of users in real-time as they connect using Socket IO

I've been working on setting up a system in Socket IO to create a list of individuals who join a 'party room'. The plan is to lock the party room once all players are present, and then display views to users. However, I've hit a roadblo ...

A guide on choosing a custom button color and automatically reverting to its original color when another button is clicked

I have a collection of 24 buttons, all in a dark grey (#333333) shade. Whenever I click on one of the buttons, it changes to a vibrant blue color (#0099ff), which is functioning correctly. However, when I proceed to click on another button, the previous ...

How to extract the HTML content from specific text nodes using Javascript

I have a piece of HTML that looks like this: <div id="editable_phrase"> <span data-id="42">My</span> <span data-id="43">very</span> <span data-id="1">first</span> <span data-id="21">phrase< ...

What could be causing the issue where a POST request from angular.js fails to work, but when sent directly from a form

In my current project, I am implementing basic authentication in node.js with passport.js using the LocalStrategy method. Although password validation has not been incorporated yet, user accounts are being stored in a MongoDB instance. I encountered a maj ...

Converting coordinates of latitude and longitude into JSON format

I'm currently facing some challenges with organizing map waypoints, defined by latitude/longitude pairs, within a JSON array. This is the progress I've made so far. var llData = {}; var waypointData = {}; for (i = 0; i < routeArray.length; ...

Tips for refreshing a D3.js bubble chart with live JSON data updates

Currently delving into d3 and experimenting with transforming a static bubble chart into a dynamic one that adjusts by removing or adding bubbles based on JSON changes. I am aiming to have the JSON file refreshed every 5 seconds to update the bubble chart ...

Steps to make a pop-up window for text copying by users

On my website, I have a link that users need to copy for various purposes. I want to provide an easy way for them to see the link and then manually copy it to their clipboard. Instead of using code to copy to the clipboard, I am looking for a solution whe ...

Discrepancy in sorting order of key-value objects in JavaScript

Check out my jsFiddle demonstration illustrating the issue: Example Here is the HTML structure: <select id="drop1" data-jsdrop-data="countries"></select> <select id="drop2" data-jsdrop-data="countries2"></select>​ Below is the ...

Adding icons to form fields based on the accuracy of the inputs provided

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Assignment 2 - Website Bui ...

Utilizing ReactJS to fetch data from Material-UI's <TableRow/> component upon selection - a comprehensive guide

I've integrated Material-UI's <Table/> (http://www.material-ui.com/#/components/table) component with <TableRow/> containing checkboxes in a ReactJS project. While I can successfully select rows by checking the boxes, I am struggling ...

Vuefire encountering an issue with Vue 3 and throwing a Vue.use error

After setting up a Vue app and importing Vue from the vue module, I encountered an issue: ERROR in src/main.ts:4:5 TS2339: Property 'use' does not exist on type 'typeof import("/data/data/com.termux/files/home/ishankbg.tech/node_modules/vue/ ...

Exploring ways to personalize the parsing of url query parameters in express.js

When using req.query, the hash of query parameters is returned. Additionally, if a parameter consists of a JSON object, it is automatically parsed into JSON format, which is quite impressive. However, I am curious about customizing this parsing process. I ...

JavaScript's speciality - altering the Jquery fade in and fade out effect on Firefox browsers

Utilizing Jquery, I implemented a feature on a table where moving the mouse over a row changes its color. The Javascript code was specifically designed for IE7 and works flawlessly there. However, when tested in Firefox, the text fades along with the backg ...

Why is my JQuery UI droppable accept condition failing to work?

After scouring the internet for hours, I'm still stuck and can't seem to figure out what's wrong with my code: Here's the HTML snippet: <ul style="list-style:none;cursor:default;"> <li>uuu</li> <li>aaa& ...

What is the best ECMAScript version to select for implementing the TypeScript compiler in an Electron application?

In my Electron 5.0.6 project, I currently have es3 as the default target in my tsconfig.json file. However, I received an error message indicating that I need to upgrade to at least es6 to utilize getter/setter functionality in TypeScript. I am now contem ...

Transferring a JavaScript Value to a PHP Variable (Under Certain Constraints)

Although I have reviewed existing questions and answers related to this issue, I found that the solutions provided do not suit my specific situation. Discussing the differences between client-side JavaScript and server-side PHP, two common solutions are: ...