Sequencing numerous promises (managing callbacks)

I am encountering some challenges with promises when it comes to chaining multiple ones. I'm having difficulty distinguishing how to effectively utilize promises and their differences with callbacks. I've noticed that sometimes callbacks are triggered regardless of whether a promise is resolved, which makes the implementation below unreliable (unless there's an issue with my syntax or logic).
I went through the official documentation and came up with this code snippet, but I'm unsure if it's implemented correctly.

The registration flow works as follows:

  • User selects an Alias -> Details Alias + userID (Device's Universally Unique Identifier) are sent to the server.
  • If the Alias is available, an ApiKey(token) is generated, user registered, and the response is sent back to the client side (Stored in DB).

Services.js

(function(angular) {
   myApp.factory("deviceDB.Service", ['$resource', '$http', '$q',
   function ($resource,  $http , $q ) {

    return {

//Second Promise: After API token is generated server-side, store response in the database

        RegDevice: function (alias, apiKey, userID) { 
            var deferred = $q.defer();
            var configuration ;
            var db = window.sqlitePlugin.openDatabase({name: "config.db"});
            setTimeout(function () {

                db.transaction(function (tx) {
                    tx.executeSql('CREATE TABLE IF NOT EXISTS user_details (userID UNIQUE , alias TEXT, apiKey TEXT)');
                    tx.executeSql("INSERT INTO user_details (userID, alias, apiKey) VALUES (?,?,?)", [userID, alias, apiKey], function (tx, res) {

                        deferred.resolve(configuration = true);
                    }, function (e) {
                        // console.log("ERROR: " + e.message);
                        deferred.reject(configuration = false);
                    });
                });

            }, 1000);
            return deferred.promise;
        },

//First Promise: Register user on the server side & generate API token

        RegUser: function (alias, userID) { 

            var deferred = $q.defer();
            var pro;
            pro = $resource('api/query/register', {'alias': alias, 'userID': userID},
                { query: {
                        isArray: false,
                        method: 'GET'  } });

            setTimeout(function () {
                pro.query(function (res) {
                    if (res.error) {
                        deferred.reject( { error : res.error, exists: res.exists,  msg: res.message } );
                    }
                    else {
                        deferred.resolve( {error : res.error , alias: res.alias , apiKey: res.apiKey, msg: res.message } );
                    }
                }, function (e) {
                    deferred.reject( { errorStatus: e.status } );

                });

            }, 1000);
            return deferred.promise;
        }

    };

  }]);

}(window.angular));


Now, in My controller, I would like to chain both promises mentioned above. In the Documentation, it states:

then(successCallback, errorCallback, notifyCallback)
– no matter when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication before the promise is resolved or rejected.

  1. What is the purpose of using Callbacks if they can be triggered regardless of whether the Promise is resolved?
  2. Shouldn't I call for example Promise2 within the first Promise's Success Callback? If it is triggered irrespective of whether Promise1 is resolved, how can I chain Promise2 so that it is executed only when Promise1 is resolved?


What I have attempted:
Controller.js

myApp.controller('RegisterController', ['$scope', '$http', 'deviceDB.Service',
    function ($scope , $http , deviceDB.Service) {

   var Promise1 = deviceDB.RegUser($scope.alias, $scope.Device); 

// First promise - Validate with server
   Promise1.then(function(data)
                    {
                        console.log(' Registration Server-Side successfully');
                        $scope.apiKey = data.apiKey;
                        term.echo(data.apiKey);

                    }, function(e)
                    {
                        console.log('Registration Failed');
                        term.echo(e.msg);

                    })

//Call Promise 2 & Store details Client-Side using .then()

    .then(deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device), 
     function(d){
                       console.log('Items Stored in DB successfully');
                    }, function()
                    {
                        console.log('Items Stored in DB Failed');
                    });
  }]);

Notes: I understand storing details client-side is not recommended practice, however, I am exploring a different concept (anonymous messaging) where there are no security concerns..

Thank you for taking the time to read this.

Answer №1

Your second call after then appears to be incorrect.

//Initiate Promise 2 & Save client-side details using .then()

The then function can take up to three parameters:

then(successCallback, errorCallback, notifyCallback)
. You have passed in
deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device)
, which gets evaluated immediately and the resulting promise is mistakenly used as the success function. Your actual success function is being treated as the error callback, and your failure function as the notify callback.

I suggest trying the following approach:

Promise1.then(function(data)
{
   console.log(' Registration server-side successful');
   $scope.apiKey = data.apiKey;
   term.echo(data.apiKey);

   return deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device);

}, function(e)
{
   console.log('Registration failed');
   term.echo(e.msg);

   return e;

}).then(function(d) {/*all good*/}, function(e) {/* all bad */}

Note how the call to RegDevice is now wrapped within a function block, ensuring that a promise is returned from the then block for proper chaining.

Answer №2

When it comes to chaining promises, $q.serial stands out as a fantastic library in my opinion. It simplifies the process and even takes care of ensuring that all promises in the chain are legitimate.

Let's take a look at a brief example:

function do_all() {                                                         
    var task_1 = function() {                                                  
        return $http.get("some url")                                           
            .then(on_xhr_completed_fn, on_xhr_failed_fn);                      
    }                                                                          

    var task_2 = function(some_data) {                                         
        vm.bla = some_data;                                                    
        return $http.get("other url")                                          
            .then(on_xhr_completed_fn, on_xhr_failed_fn);                      
    }                                                                          

    var task_3 = function(other_data) {                                        
        vm.bli = other_data;                                                   
    }                                                                       

    var tasks = [task_1, task_2, task_3];                                      

    return $q.serial(tasks)                                                    
        .then(function() {                                                     
            console.log("Finished tasks 1, 2 and 3!!!");                       
        });                                                                    
}  

Answer №3

Consider this async/await based approach for tackling the issue:

async function execute_task_A(params) {
    return new Promise((resolve, reject) => {
        return resolve(returned_value)
    });
}

async function execute_task_B(params) {
    return new Promise((resolve, reject) => {
        return resolve(returned_value)
    });
}

async function execute_task_C(params) {
    return new Promise((resolve, reject) => {
        return resolve(returned_value)
    });
}

async function execute_multiple_async_tasks(identifier) {
    let a = execute_task_A(identifier);
    let b = execute_task_B(a);
    let c = execute_task_C(b);
    return c;
}

return Promise.resolve()
    .then(() => {
        let result_c = (async () => {
            let c_result = await execute_multiple_async_tasks(identifier)
            return c_result;
        })();
        return result_c;
    })
    .then((result_c) => {
        return result_c;
    })
    .catch((error) => {
        console.log(error);
    });

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

Tips for handling two JSON array objects with JavaScript

This particular function public function groups_priviledges($user_id = NULL){ $data['groups'] = $this->priviledges_model->admin_groups(); $data['member'] = $this->priviledges_model->empAdminGroup($user_id); echo ...

Store the incoming documents from xmpp using the Strophe si-filetransfer feature

I am currently working on adding file transfer functionality to my web application using the strophe.si-filetransfer.js plugin. I have successfully received file details within an iq stanza. My inquiry is, how can I extract the file data from this iq stanz ...

What could be the reason for a code running successfully in node but not in the REPL environment?

This is the current script I'm working with: const lib = require('./lib.js'); const fs = require('fs'); const graph = fs.readFileSync('../js-working-dir/add_graph.pb', 'utf8'); const sess = new lib.Session(gr ...

Unable to modify $scope value within the directive

This special function triggers once ng-repeat is done iterating through the list using scope.$last: simpleSearchApp.directive('afterResults', function($document) { return function(scope, element, attrs) { if (scope.$last){ scope.windowHe ...

Issue encountered when calling theme.breakpoints.down('') function from Material UI

As a novice, I have ventured into using material UI on the front-end of my project. My aim is to achieve responsiveness by leveraging theme.breakpoints.down as indicated in the material UI documentation. However, when attempting to implement this, I encoun ...

AngularJS controller updating only a portion of the dataset

My small bottle server is capable of returning random values based on a specific machineID. Here's how it works: @app.route('/dataMachine') @enable_cors def simulatedMachineData(): prevVals = {'machineID_1': 0,'machineID ...

Understanding the functionality of scope in AngularJS is essential for mastering the framework

Why does this code work? app.controller("ctrl", function($scope){ $scope.From = "Santa"; $scope.To = "Claus"; }); And why doesn't this one work? app.controller("ctrl", function(scope){ scope.From = "Santa"; scope.To = "Claus"; }); ...

Is there a way for me to confirm if a node module is compatible with bundling in Webpack and running in a web browser?

One of the advantages of using Webpack is its ability to bundle up node modules for use in an HTML page within a browser. However, not all node modules are compatible for this purpose. Certain modules, such as those utilizing the 'fs' module or n ...

Stylus remains undefined even after a successful npm installation

I've been following a tutorial to learn more about node.js, but I keep encountering a strange error. After running npm install stylus, I receive the following output: npm http GET https://registry.npmjs.org/stylus npm http 304 https://registry.npmjs. ...

Is there a way I can create a conditional statement to determine the values of my selection?

I need to customize the order status values for 'confirmed', 'on the way' and 'delivered'. How can I map these statuses to specific numerical values based on the options available in a select menu? Confirmed - 10 On the way - ...

Personalized verification based on a different input

In a simple idea, I have two inputs that I validate only when both are touched. At least one input needs to be different from 0. Where could the issue lie? The HTML <div class="form-group" ng-class="{'has-error' : (myForm.$submitted || myFor ...

Dynamically generating fields in JavaScript causes the fields to mysteriously vanish

Below is the JavaScript code I am working with: <script language="javascript"> function addInput() { document.getElementById('text').innerHTML += "<input type='text' value='' name='a1[]' size='60&a ...

I designed my higher-order component to allow for dual invocations. How can I integrate Redux within this framework?

I have implemented my higher-order component (HOC) in such a way that it can be invoked twice, emphasizing the concept of "functional programming". However, I am facing challenges in connecting Redux to access state and certain functions. I would greatly ...

What is the best way to apply a CSS class to a div element without affecting its child elements using JavaScript?

Currently, I am using JavaScript to add and remove a CSS class through a click event. Within my div structure, I have multiple similar divs. While I can successfully add and remove the class from my main div, I am facing an issue where the class is also ap ...

Page showing without banner

As I scroll down through my website, I want a specific banner to appear when I reach the contact page. The banner will show a goodbye message and give users the option to close it or keep it open. For example: (function() { requestAnimationFrame(fu ...

Vue Single Page Application - invoking methods across all components

I am currently developing a notification feature that can be triggered from any component. It utilizes a straightforward Vuetify v-snackbar. Within App.vue <router-view :key="$route.fullPath"></router-view> <v-snackbar :valu ...

What is the best way to transmit a two-dimensional array using ajax?

Here is the process I use to send data to the server: var points = []; var coords = polyline.geometry.getCoordinates(); for (var i = 0; i < coords.length; i++) { var x = (coords[i][0]).toFixed(4); var y = (coords[i][1]).toFixed(4); points[i ...

Is there a way to ensure async ngOnInit finishes before running tests?

I'm struggling with handling async work in my Angular component's ngOnInit method. Is there a way to ensure that my expect statements run only after all the async tasks are complete? it('Ensure correct values after async tasks in ngOnIni ...

Utilize jQuery ajax to target a particular recurring input

I am using HTML code along with another jQuery loop. Within this loop, there is an input element that is also being looped. I am trying to manipulate it using jQuery Ajax... but I am facing an issue where only the first input element works properly. The re ...

Creating a circle in SVG that cannot be scaled using Javascript

I'm working on a map project using JavaScript and SVG for drawing the lines. One feature I'd like to implement is the ability to search for a specific road, and if found, display a circle on the map. I understand how to draw a circle in SVG, bu ...