Tips for altering promises within nested for loops?

Presented below is a complex function that aims to extract model variants from a csv file, which are stored as strings in an array. The path of this csv file depends on information obtained from other csv files, hence the need for looping.

The csvService.getCsvAsArray call retrieves the contents of a csv file as an object, with each column stored as an array under an attribute named after the top column. When you encounter expressions like

result["NavigationSectionShortNames"]
, remember that it represents an array of strings.

    var INDEX_OF_PRODUCTS_SECTION = 1;
    var getAllModelVariants = function () {
        return csvService.getCsvAsArray("Contents/Sections/" + navFileNames[INDEX_OF_PRODUCTS_SECTION] + "/order.csv").then(function (result) {
            var products = [];
            for (var i = 0; i < result["NavigationSectionShortNames"].length; i++) {
                csvService.getCsvAsArray("Contents/Sections/" + navFileNames[INDEX_OF_PRODUCTS_SECTION] + "/" + result["NavigationSectionShortNames"][i]
                + "/order.csv").then(function (productModelInfo) {
                    var productFamilies = []; 
                    for (var j = 0; j < productModelInfo["NavigationSectionShortNames"].length; j++) {
                        csvService.getCsvAsArray("Contents/Sections/" + navFileNames[INDEX_OF_PRODUCTS_SECTION].length + "/" + result["NavigationSectionShortNames"][i] + "/" + productModelInfo["NavigationSectionShortNames"][j] + "/order.csv").then(function (modelVariants) {
                            var productModels = []; 
                            for (var k = 0; k < modelVariants.length; k++) {
                                productModels.push(modelVariants["NavigationSections"][k]);
                            };
                            productFamilies.push(productModels);
                        });
                    };
                    products.push(productFamilies);
                });

            };
        })
        return products;
    };

This current approach may not work due to the asynchronous nature of promises causing variable increments to change before resolution. Is there a solution for using promises effectively in nested loops like these? I have considered using $q.all but applying it to my function is challenging. As I explore options, I found an example on using promises in a single loop at this link.

My objective is to return a 3D array structure, exemplified in the plunker linked below for reference.

If needed, here's a simpler version of the function without promises:

    var getAllModelVariantsTest = function () {
        var result = ["productFamily1", "productFamily2", "productFamily3"];
        var testArray = ["productFamilyModel1", "productFamilyModelt2", "productFamilyModel3", "productFamilyModel4"];
        var testArray3 = ["productFamilyModelVariant1", "productFamilyModelVariant2", "productFamilyModelVariant3", "productFamilyModelVariant4"];
            var products = [];
            for (var i = 0; i < result.length; i++) {
                    var productFamilies = [];
                    for (var j = 0; j < testArray.length; j++) {
                            var productModels = [];
                            for (var k = 0; k < testArray3.length; k++) {
                                productModels.push(testArray3[k]);
                            };
                            productFamilies.push(productModels);
                    };
                products.push(productFamilies);
            };
            return products;
    };
    var testOutput = getAllModelVariantsTest();

You can view the desired output by running the above code snippet or accessing the plunker demo through this link.

I seek assistance in implementing promises within nested loops successfully to achieve results akin to the simplified function version. Is utilizing $q.all the recommended course of action?

Your insights and expertise are highly appreciated. Feel free to request additional details if needed.

Answer №1

Solution for Dealing with Nested Callbacks or Pyramid of Doom:

function fetchAllDataForProduct(product) {

  var getProductFamilies = (product) => Promise.resolve(["productFamily1", "productFamily2", "productFamily3"]);
  var getFamilyModels    = (productFamily) => Promise.resolve(["productFamilyModel1", "productFamilyModelt2", "productFamilyModel3", "productFamilyModel4"]);
  var getModelVariants   = (familyModel) => Promise.resolve(["productFamilyModelVariant1", "productFamilyModelVariant2", "productFamilyModelVariant3", "productFamilyModelVariant4"]);
  var processVariant     = (modelVariant) => modelVariant;
  var mapFunctionArray = ( func ) => (array) => array.map( func ); // mapFunctionArray( element => element )( [1,2,3,4] )

  return getProductFamilies(product)
    .then( productFamilies => productFamilies
      .map( family => getFamilyModels(family)
        .then( familyModels => familyModels
          .map( model => getModelVariants(model)
            .then( variants => variants
              .map( processVariant )
            )
          )
        )
      )
    );
}

fetchAllDataForProduct('exampleProduct').then(
  results => {
    console.log(results);
  }
);

getFoos returns a Promise.
We call then on it which returns another Promise.
Inside then, we call foos.map(..getBars().then..) which returns an array of promises.
Inside then, we call bars.map(..getGoos.then..).
Goos promises eventually resolve to results.

The structure you receive is as follows (* denotes an array):

productPromise:
  *familyPromise:
    *modelsPromise:
      *processedVariant

To eliminate the issue of nested callbacks, consider using the async/await features of EcmaScript7, currently accessible through transpilers like Babel.

async function fetchAllDataForProduct(product) {

  var getProductFamilies = (product) => Promise.resolve(["productFamily1", "productFamily2", "productFamily3"]);
  var getFamilyModels    = (productFamily) => Promise.resolve(["productFamilyModel1", "productFamilyModelt2", "productFamilyModel3", "productFamilyModel4"]);
  var getModelVariants   = (familyModel) => Promise.resolve(["productFamilyModelVariant1", "productFamilyModelVariant2", "productFamilyModelVariant3", "productFamilyModelVariant4"]);
  var processVariant     = (modelVariant) => modelVariant;

  var productFamilies = await getProductFamilies(product);
  var promises = productFamilies.map( async(family) => {
    var familyModels = await getFamilyModels(family)
    return familyModels.map( async(model) => {
      var variants = await getModelVariants(model);
      return variants.map( processVariant )
    })
  });
  return Promise.all(promises);
}

fetchAllDataForProduct('example').then(
  results => {
    for(var promise of results) {
      promise.then(...)
    }
  }
);

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

Is there a way to have AngularJS display the date without factoring in time zones?

When retrieving a date from WEBapi, it returns in the format: 2013-01-01T00:00:00 I need it to display as: {{msa.StartDate | date:'yyyy-MM'}} Currently, it shows as: 2012-12 Is there a way to make it ignore time zones and display as: 2013- ...

What is the best way to enable and disable the edit-in-place feature using jQuery?

Recently, I decided to experiment with the jQuery In Place Editor but I've come across an issue I can't seem to solve. Below is the code snippet that I have been working on. In my implementation, I am utilizing the jQuery toggle method to enable ...

Incorporate React JS seamlessly into your current webpage

As I delve into learning React and considering migrating existing applications to React, my goal is to incorporate a React component within an established page that already contains its own HTML and JavaScript - similar to how KnockoutJS's `applyBindi ...

Using JQUERY to create a dropdown menu that dynamically changes based on the date field

I am currently working on a JQUERY project and facing a challenging situation. I usually know how to create dropdown menus that depend on one another, but this time I need a dropdown menu that displays age ranges based on the date entered in the birth-date ...

Components can be iterated over without the use of arrays

In my component, I have a render that generates multiple components and I need some of them to be repeated. The issue I am facing is that when I create an HTML render, the variable is not being processed and it just displays as text. Although the actual c ...

Is there a way to execute this process twice without duplicating the code and manually adjusting the variables?

I'm looking to modify this code so that it can be used for multiple calendars simultaneously. For example, I want something similar to the following: Here is what I currently have: This is my JavaScript code: var Calendar = { start: function () ...

Ember Component Incorporating Keyboard Input

I recently developed an ember component that features a rectangular block in a green canvas. Everything is working smoothly so far. However, I am facing some challenges with implementing keyboard input commands (A S D W) to navigate the rectangle within t ...

Issue with Angular.js: value() function not being injected within the config() function

Similar Question: How to inject value into module.config in AngularJS? I'm facing an issue with injecting the value() into app.config(). Below is the code snippet (written in coffeescript) window.app = angular.module("app", []) app.value("templ ...

How do I assign a default value to an optional parameter in a derived class in Typescript?

One of my classes is called ClientBase: export class ClientBase { constructor(private uri: string, private httpClient: HttpClient) { } // Contains Various Methods } I have multiple subclasses that are derived from the ClientBase For instance: @I ...

Elegant CSS background image fade effect

Having a small JS script that functions properly, but encountering an issue when quickly hovering over buttons causing the smooth transition effect to not work as desired. This abrupt change in image is quite unappealing. Any help would be greatly appreci ...

Implications of using literals, objects, or classes as arguments in functions that are called multiple times can have

Context A project I'm working on involves a scenario where a Shape class is triggering a function call SetPosition( x, y ) on a Drawer class. As part of this process, the Drawer class needs to retain the values (x, y) passed through SetPosition. The ...

What is causing the endless $digest loop when using $locationProvider.html5Mode?

When I enable $locationProvider.html5Mode(true), my application throws an error message: 10 $digest() iterations reached. Aborting! (...). Below are snippets of my code. Here is my routes configuration: function configRoutes($stateProvider, $urlRouterPro ...

Server experiencing slow performance with Node.js formidable file uploads

When sending files as form data along with some fields using an http post request in angular.js and receiving file in app.post in node.js, the performance is inconsistent. While local testing shows a fast upload speed of 500 mb/sec with formidable, on the ...

How can you retrieve the property value from an object stored in a Set?

Consider this scenario: SomeItem represents the model for an object (which could be modeled as an interface in Typescript or as an imaginary item with the form of SomeItem in untyped land). Let's say we have a Set: mySet = new Set([{item: SomeItem, s ...

What is the process for creating an Account SAS token for Azure Storage?

My goal is to have access to all containers and blobs in storage. The Account SAS token will be generated server-side within my Node.js code, and the client will obtain it by calling the API I created. While Azure Shell allows manual creation of a SAS toke ...

Modifying the $scope within a controller

I need to update the $scope within the controller based on the object that is being clicked. The current code looks like this: var blogApp = angular.module('blogApp', ['ngSanitize', 'ngRoute']); blogApp.controller('blog ...

The issue persists with multiple instances of Swiper js when trying to use append and prepend functions

I'm encountering an issue with my swiper carousels on the page. Despite everything working correctly, I am facing problems with the append and prepend slide functions. When I remove this part of the code, everything functions as expected. $('.s ...

Modify File Name with Fine Uploader: Personalize Your File Titles

When attempting to save files with a specific directory structure in my S3 bucket, I am encountering an issue where the getName method only returns the reference instead of the actual value of the file name. The output of getName is displayed as [object O ...

What is the best way to access dropdown sub-menu options from a complex multi-level navigation bar

Struggling to figure out how to open my dropdown sub-menu using CSS. Hoping to make it so the user can open it by hovering over the corresponding tag. I attempted to use #lablinksDD to trigger the opening of #ddSbMenu when hovered over #menuDD with #labLin ...

New and improved method for showcasing the switching of two PHP variables using jQuery Ajax

Obtaining two random values from a table in the same row can be achieved using PHP and MySQL. For example: <?php $result = mysql_query("SELECT * FROM people ORDER BY RAND() LIMIT 1", $connection); $row = mysql_fetch_array($result); echo $ro ...