Combining Multiple Optional Async AJAX Calls

In my Angular 1.5.8 app, I have different views that require various combinations of the same 3 ajax requests. Some views need data from all three endpoints, while others only need data from one or two.

To efficiently manage the retrieval of this data, I am developing a function that will ensure each endpoint is called only when necessary. I want the ajax requests to be triggered on demand and no sooner. While I have a working function in place, I believe it can be enhanced further.

The function below resides within the $rootScope. It utilizes the fetchData() function to cycle through the get requests as needed. Once data is fetched, it is stored in the global variable $rootScope.appData and then fetchData() is invoked again. When all required data is obtained, the deferred promise is resolved and the data is returned to the controller.

$rootScope.appData = {};

$rootScope.loadAppData = function(fetch) {
  var deferred = $q.defer();

  function getUser() {
    $http
      .get('https://example.com/api/getUser')
      .success(function(result){
        $rootScope.appData.currentUser = result;
        fetchData();
      });
  }

  function getPricing() {
    $http
      .get('https://example.com/api/getPricing')
      .success(function(result) {
        $rootScope.appData.pricing = result;
        fetchData();
      });
  }

  function getBilling() {
     $http
       .get('https://example.com/api/getBilling')
       .success(function(result) {
         $rootScope.appData.billing = result;
         fetchData();
       });
  }

  function fetchData() {
    if (fetch.user && !$rootScope.appData.currentUser) {
      getUser();
    } else if (fetch.pricing && !$rootScope.appData.pricing) {
      getPricing();
    } else if (fetch.billing && !$rootScope.appData.billing) {
      getBilling();
    } else {
      deferred.resolve($rootScope.appData);
    }
  }

  if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) {
    deferred.resolve($rootScope.appData);
  } else {
    fetchData();
  }

  return deferred.promise;
};

An object fetch is passed as an argument indicating which ajax requests to trigger. For instance, calling $rootScope.loadAppData() with only user and pricing data required would look like this:

$rootScope.loadAppData({user: true, pricing: true}).then(function(data){
   //execute view logic. 
});

I have a few questions:

  1. Could the chaining of functions be optimized? Is fetchData() adequate, or is there a better way to accomplish this functionality?
  2. Is there a method to initiate all necessary Ajax requests simultaneously, yet wait for all calls to finish before resolving the promise?
  3. Is it common practice to store data in the $rootScope?

Note: Error handling is not implemented in this function at present. I plan to incorporate error handling prior to using this code snippet, although it's not directly related to my inquiry.

Answer №1

Opt for the .then method over the .success method and make sure to return data to its success handler:

function fetchUserPromise() {
    var promise = $http
      .get('https://sample.com/api/getUser')
      .then( function onSuccess(result) {
          //return data for chaining
          return result.data;
      });
    return promise;
}

Switch to using a service instead of relying on $rootScope:

app.service("myService", function($q, $http) {

    this.loadDataForApp = function(options) {

        //Create initial promise
        var promise = $q.when({});

        //Chain from initial promise
        var p2 = promise.then(function(appData) {
            if (!options.userDetails) {
                return appData;
            } else {
                var derivedPromise = fetchUserPromise()
                  .then(function(user) {
                    appData.user = user;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });

        //chain from p2
        var p3 = p2.then(function(appData) {
            if (!options.pricingInfo) {
                return appData;
            } else {
                var derivedPromise = getPricingPromise()
                  .then(function(pricing) {
                    appData.pricing = pricing;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });
        //chain from p3
        var p4 = p3.then(function(appData) {
            if (!options.billingInfo) {
                return appData;
            } else {
                var derivedPromise = getBillingPromise()
                  .then(function(billing) {
                    appData.billing = billing;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });

        //return final promise
        return p4;
    }
});

The provided example sets up a promise with an empty object, then chains three operations. Each operation checks if fetching is required. If necessary, a fetch is performed and the result is added to the appData object; if no fetch is needed, the appData object is passed along to the subsequent operation in the chain.

USAGE:

myService.loadDataForApp({userDetails: true, pricingInfo: true})
  .then(function(appData){
    //execute view logic. 
}).catch(functon errorHandler(errorResponse) {
    console.log(errorResponse);
    throw errorResponse;
});

If any fetch operation fails, the following operations in the chain are bypassed, and the final rejection handler will be triggered.

By utilizing the .then method of a promise which returns a new derived promise, it becomes feasible to establish a chain of promises. Chains of varying lengths can be created seamlessly, as one promise can be resolved with another promise (resulting in a delay of resolution), allowing the postponement or deferment of promise resolution at any point within the chain. This functionality enables the implementation of robust APIs. -- AngularJS $q Service API Reference - Chaining Promises

Answer №2

Recently discovered an efficient method to tackle question 2 in the original post. By utilizing $q.all(), it enables promises to run concurrently, resolving only when all are completed successfully or failing immediately if one of them encounters an error. With valuable guidance from @georgeawg, I have incorporated this approach into a service. Take a look at my revised implementation below, which organizes the code into a service and executes all calls simultaneously:

services.factory('appData', function($http, $q) {
    var appData = {};
    var coreData = {};

    appData.loadAppData = function(fetch) {
      var deferred = $q.defer();
      var getUser = $q.defer();
      var getPricing = $q.defer();
      var getBilling = $q.defer();

      if (!fetch.user || coreData.currentUser) {
        getUser.resolve();
      } else {
        $http
          .get('https://example.com/api/getUser')
          .success(function(result){
            coreData.currentUser = result;
            getUser.resolve();
          }).error(function(reason) {
            getUser.reject(reason);
          });
      }

      if (!fetch.billing || coreData.billing) {
        getBilling.resolve();
      } else {
         $http
           .get('https://example.com/api/getBilling')
           .success(function(result) {
             coreData.billing = result;
             getBilling.resolve();
           }).error(function(reason) {
             getBilling.reject(reason);
           });
      }

      if (!fetch.pricing || coreData.pricing) {
        getPricing.resolve();
      } else {
         $http
           .get('https://example.com/api/getPricing')
           .success(function(result) {
             coreData.pricing = result;
             getPricing.resolve();
           }).error(function(reason) {
             getPricing.reject(reason);
           });
      }

      $q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) {
        deferred.resolve(coreData);
      }, function(reason){
        deferred.reject(reason);
      });

      return deferred.promise;
    };

    return appData;
  });

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

Sharing models between AngularJS controllers

I am currently in the process of developing an application that will showcase various charts and controls to filter the presented data. I have structured the HTML page in a way that remains consistent throughout, leading me to consider creating an HTML tem ...

Tips for creating a custom script in my React Native application

My React Native app requires a script to generate static files during the release process. The app is a game that utilizes pre-computed boards, which are resource-intensive to compute. Therefore, I am developing a script that will create these boards and s ...

How can I dynamically populate a select menu in HTML without the need to submit any data

Looking for help with an input form in HTML that uses a combination of input and select boxes. My goal is to dynamically populate a select menu based on the data entered into the input boxes. For example: Employee One: Jim Employee Two: John Employee Thre ...

The fading effects are not functioning as expected in the following code

Hey there, I'm not the most tech-savvy person, but I managed to come up with this code for page redirection. Unfortunately, I couldn't quite get the fade out and fade in effects to work properly when executing it. If anyone out there can help me ...

Display fixed information in a separate tab using express and ejs

I have an Express app that is running smoothly with a MongoDB backend, and it's able to render tables with data flawlessly. However, I've encountered an issue when trying to export a table from my database. The exported table looks something like ...

Using JavaScript to create CSS animations triggered on hover

Looking for a CSS Animation that will play forward once when the mouse enters a specific div, and then play in reverse when the mouse leaves. Check out my JsFiddle here. The div with the class ".item" should trigger the mouse enter event. The animation co ...

What is the process for removing a key from an object and replacing it with its corresponding value?

My JSON sample looks like this: var obj={ "results":{ "grade":"A", "marks":12 }, "data":{ "name":"sam", "gender":"male", "age":10 } }; I am trying to transform the above JSON to: var obj={ "res ...

Tips for coordinating the execution of 2 async.waterfalls in Node.js

I have a series of read commands that need to be executed in sequence. If any of them fail, the processing stops. The array readCommands contains functions for reading... async.waterfall(readCommands, function(err) { if (err) { console.log(e ...

Trigger JavaScript when a specific division is loaded within a Rails 4 application

Is there a way to trigger a JavaScript function when a specific div with a certain class is loaded within my Rails 4 application? <div class="myClass"> hello world </div I am looking for a solution to execute some JavaScript code only when t ...

Instead of using setTimeout in useEffect to wait for props, opt for an alternative

Looking for a more efficient alternative to using setTimeout in conjunction with props and the useEffect() hook. Currently, the code is functional: const sessionCookie = getCookie('_session'); const { verifiedEmail } = props.credentials; const [l ...

Selecting a date in Jade's date picker

I'm currently facing an issue with getting a datepicker to function properly using Jade in a node.js and express framework. Within index.jade, I am loading the necessary javascript files like this: link(type='text/css', href='css/ui-l ...

Navigating through options in angularjs with a dropdown for effective searching

I need help with implementing a dropdown search filter for my folder and category lists. Here is the code I currently have: <!--folder start--> <div class="form-group"> <div class="col-sm-2"> <select class="form-control" ...

If the PHP variable evaluates to true, then a CJuiDialog in Yii view will open

I am attempting to open the CJuiDialog if the $check value equals true in the view part. <?php if($check==true) { js:function(){$('#dialogDisplay').dialog('open');} } $this->beginWidget('zii.widgets.jui.CJuiDialog', ...

It is imperative that the HTML div element remains within the boundaries of the page

Let me begin by providing some context. I am currently developing a responsive page that uses percentages to position my divs, allowing users to drag and drop items wherever they please. However, a problem arises when the user positions an object too close ...

Issue encountered with the triggerWord <input> and JavaScript

I am struggling to get the page http://example.com to load when I type trigger in the <input> text box. It was working at some point after a few modifications, but now it doesn't seem to be functioning properly. Can anyone help me figure out wh ...

Manipulate values within an array when a checkbox is selected or deselected

I am developing an Angular application where I have created a checkbox that captures the change event and adds the checked value to an array. The issue I am facing is that even if the checkbox is unchecked, the object is still being added to the array. D ...

The resize function triggers twice as many events with each window resize

While working on my website, I encountered a navigation issue when resizing the browser from desktop to mobile size. Initially, the mobile menu worked on load, as did the desktop navigation. However, after running a script with $(window).on('resize&ap ...

The index-hml-webpack-plugin is throwing an error: EISDIR - attempting to read from a directory, which is an illegal

the issue arises with the index-html-webpack-plugin plugin Despite numerous attempts to update my code and configuration, I have been unsuccessful in addressing this error. Any assistance in resolving this matter would be greatly appreciated. Thank you in ...

Can you share any recommendations or instances of modifying data within HTML tables using Laravel?

Has anyone ever needed to directly edit and update data in a HTML table using Laravel? I have successfully created "create" tables for different tasks, but I'm interested in being able to modify the data directly on an "index" page. While there are ...

Troubleshooting unexpected issues with dynamically updating HTML using innerHTML

Need help with a renderWorkoutUpdated function that replaces specific workout records with updated values. _renderWorkoutUpdated(currentWorkout) { let html = `<li class="workout workout--${currentWorkout.type}" data-id="${ curre ...