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

Creating a specialized Angular directive for handling input of positive numbers

I am working on an application that requires a text field to only accept positive integers (no decimals, no negatives). The user should be restricted to entering values between 1 and 9999. <input type="text" min="0" max="99" number-mask=""> While s ...

Storing a JSON response in a variable

I need assistance with using the Google Geocoder API to obtain the longitude and latitude of an address for a mapping application. How can I retrieve data from a geocode request, like this example: "http://maps.googleapis.com/maps/api/geocode/json?address ...

Is it possible to implement an automatic clicking function on a bootstrap navigation bar similar to a carousel?

I am working on a website with a bootstrap navigation bar similar to the one shown in this image : bootstrap nav <ul class="nav nav-tabs" id="tab_home" role="tablist"> <li class="nav-item" role="pres ...

Navigating through root and sub applications within AngularJS can be managed effectively when housed in sub directories

I currently have two AngularJS applications in place: The first application serves as the base and operates at the root / The second application is a sub-application that needs to be isolated for design purposes, running within a subfolder such as /foo/ ...

Error when using jQuery AJAX to parse JSONP callback

Take a look at this code snippet: $(function () { $.ajax({ type: "GET", url: "http://mosaicco.force.com/siteforce/activeretailaccounts", dataType: "jsonp", crossDomain: true, jsonp: "callback", error: f ...

The AJAX email submission form is not functioning properly

Recently, I have upgraded my email sign-up form on the website to include validation. However, after adding the validation, the form no longer sends and an error message is not displayed. Upon inspecting, I found the following error: TypeError: null is ...

Using jQuery to select all child elements based on a specified condition

Is there a way to locate all instances of .post-comment-replies that have a nested '.post-comment-reply' within them, rather than being at the first level? Currently, the code provided retrieves not only those .post-comment-replies with nested . ...

How can you open a popover in Ionic from two different triggers but have it appear in the same place?

I am facing an issue with two buttons that trigger the same popover. The problem arises when I click on the second button, a popover is displayed in the wrong place (shown by the big blue plus icon) instead of the right corner (as shown in the second image ...

I am unable to incorporate the RobotJS module into my ElectronJS project

Currently, I am working on a Windows desktop application using ElectronJS. My main challenge is integrating the RobotJS module into my project. Despite successfully downloading the module with 'npm install robotjs' and incorporating it into my ma ...

Skip waiting for all resolves to complete before showing the views in ui-router

My current setup involves using ui-router to load various subviews within a certain state. However, some of these views require resources that have a long resolution time. I want to display the other views as soon as they are ready. This is how I am fetch ...

Setting up Access-Control-Allow-Origin in WildFly 8.1.0 configuration

I am attempting to configure Access-Control-Allow-Origin on a Wildfly 8.1.0 server using the standalone.xml file provided below: <?xml version='1.0' encoding='UTF-8'?> <server xmlns="urn:jboss:domain:2.1"> <extensio ...

How can I differentiate between server-side and client-side JavaScript in Node/Express?

I decided to challenge myself by working on a JavaScript project to enhance my skills, but I am facing some difficulties distinguishing between the client-side and server-side code. Currently, the setup involves a Node app with ExpressJS as a dependency. ...

What are the differences between a Chrome app and extension? Is there any other way to access the tabs currently open in your

I need to develop an app that can access the tabs a user has open, but I'm struggling to find a way to do so without having my app run in Chrome itself. Creating an extension restricts the UI significantly, which is problematic since my app requires a ...

Tips for utilizing a unique JavaScript function with Mongoose's findByIdAndUpdate by incorporating $set:

Is it possible to update a database document using a custom JavaScript function? I am aware of the traditional method where you find -> update the document with JavaScript -> save, but this can lead to issues with concurrent data updates. Below is an examp ...

Tips for activating just a single event, whether it's 'change' or 'click'

I am facing an issue with a number input tag that has two event listeners 'on-click' and 'on-change'. Whenever I click on the arrow, both event listeners get triggered. I want only one event to trigger first without removing any of the ...

Challenge with Angular *ngFor: Struggling to Access Previous Elements

In my angular and node application, I am using socket.io. When a user joins the room, they can see their username in the user list. If another user joins, the first user can see both usernames but the new user can only see their own. This pattern continues ...

Is it within the realm of possibility for a route-handling function to accept more than two parameters?

Recently, I started learning about node js and delved into the world of jwt authentication. While going through some code snippets, I came across a request handler in express that caught my attention. app.post('/login',function(req,res){...}); ...

Updating the parent's data by modifying data in the child component in VueJS

When I use multiple components together for reusability, the main component performs an AJAX call to retrieve data. This data is then passed down through different components in a chain of events. <input-clone endpoint="/api/website/{{ $website->id ...

How to Retrieve the Value of the First Drop Down that is Linked to a Second Drop Down Using Angular JS

How can I use AngularJS to retrieve the second select drop-down value based on the selection made in the first select drop-down? Hello, I have set up two dropdown fields in MyPlunker and within the ng-option, I am applying a filter "| filter:flterWithKp" ...

Finding Location Information Based on Latitude and Longitude Using Jquery

Does anyone know of a reliable solution or Jquery plugin that can provide location details based on latitude and longitude coordinates? I heard about the Google Reverse Location API but hoping for a pre-made option. I'm not sure if there are any free ...