2

So I have a longrunning query status page for my company, and it shows bars for the different database instances that change color based on number of longrunners + other criteria.

The issue is, every time the call is made to update the info, the colors all go back to default, and build from the ground up. This is because I'm using a $scope.variable object to hold the color information as the longrunner data is retrieved.

I want to switch this to using a local standard variable within the function, and only after all data has been retrieved, assign this variable to the $scope.variable.

  • Context - our instances are organized into swimlanes, so I create an object for the swimlane color and for the instance color. When all are collapsed, you only see the swimlane, so I needed a way for the instance color to bubble up to the swimlane.

So it amounts to something like this:

var getLongrunners = function(){

        $scope.longrunnersByInstance = {};

        for (var l = 0; l < $scope.swimlanes.length; l++){
            $scope.slColor[$scope.swimlanes[l].swimlane] = 0;
        }

        for (var j = 0; j < instances.length; j++){
            $scope.longrunnersByInstance[instances[j].instance] = [];
            $scope.instanceColor[instances[j].instance] = 0;

        }



        for (var i = 0; i < instances.length; i++){
            (function(e){
                $http
                    .get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}})
                    .then(function(response){
                        var longrunners = response.data;
                        for(var k = 0; k < longrunners.length; k++){
                            $scope.longrunnersByInstance[instances[e].instance].push(longrunners[k]);
                        }

                        if(longrunners.length > $scope.dangerThresh){

                            $scope.instanceColor[instances[e].instance] = 2;

                        }else if(longrunners.length >= $scope.warningThresh){

                            $scope.instanceColor[instances[e].instance] = 1;

                        }

                        if($scope.slColor[instances[e].swimlane] < $scope.instanceColor[instances[e].instance]) {
                            $scope.slColor[instances[e].swimlane] = $scope.instanceColor[instances[e].instance]
                        }
                    },getLongrunnersFail);
            }(i));

So I want the $scope.slColor and $scope.instanceColor to be regular local variables until this loop finishes.

I look into promises, but that seemed to only be useful with $http before .then() is called on it.

Is there a way to make a custom promise type architecture and contain more than one function, and only return the promise when everything has been completed?

Thanks!

EDIT:

Most recent try:

var promises = [];
    var longrunnersByInstance = {};
    var instancesPerf = {};
    var slColor = {};
    var instanceColor = {};

    var promiseTest = function() {

        $scope.longrunnersByInstance = {};

        for (var l = 0; l < $scope.swimlanes.length; l++){
            slColor[$scope.swimlanes[l].swimlane] = 0;
        }

        for (var j = 0; j < instances.length; j++){
            instanceColor[instances[j].instance] = 0;

        }
        instances.forEach(function (instance) {
            promises.push($http
                .get('/getLongrunners', {
                    params: {envFlag: 'bh', instance: instance.instance}
                })
                .then(function (response) {
                    var longrunners = response.data;

                    longrunnersByInstance[instance.instance] = [];

                    for (var k = 0; k < longrunners.length; k++) {
                        longrunnersByInstance[instance.instance].push(longrunners[k]);
                    }

                    if (longrunners.length > $scope.dangerThresh) {

                        instanceColor[instance.instance] = 2;

                    } else if (longrunners.length >= $scope.warningThresh) {

                        instanceColor[instance.instance] = 1;

                    }

                    console.log(instance.instance);

                    if (slColor[instance.swimlane] < instanceColor[instance.instance]) {
                        slColor[instance.swimlane] = instanceColor[instance.instance]
                    }

                    return true;

                }, getLongrunnersFail)
            );

            function getLongrunnersFail(response){
                console.log("getting longrunners failed" + response.status);
            }


            $q.all(promises).then(function () {
                // longrunnersByInstance to $scope

                console.log('calling all promises callback!');

                instances.forEach(function (instance) {
                    $scope.longrunnersByInstance[instance.instance] = longrunnersByInstance[instance.instance];
                });



                // instancesPerf to $scope
                instances.forEach(function (instance) {
                    $scope.instancesPerf[instance.instance] = instancesPerf[instance.instance];
                });

                // slColor to $scope
                instances.forEach(function (instance) {
                    $scope.slColor[instance.instance] = slColor[instance.instance];
                });

                // instanceColor to $scope
                instances.forEach(function (instance) {
                    $scope.instanceColor[instance.instance] = instanceColor[instance.instance];
                });


            }, allPromisesFail);

            function allPromisesFail(){
                console.log("all promises failed")
            }
        });
    };
2
  • Why do you have the code inside the instances loop wrapped in a immediately-called anynomous function? Commented Jan 19, 2016 at 16:18
  • to maintain the integrity of the 'i' variable throughout the codeblock within the iife. Without that, I was getting funky results due to the asynchronous calls within the loop. Commented Jan 19, 2016 at 16:20

2 Answers 2

3

Angular uses the $q service for dealing with promises.

It has a function called all to deal with exactly the type of problem you encountered.

here is a simple fiddle to demonstrate it: http://jsfiddle.net/ThomasBurleson/QqKuk/

var myApp = angular.module('myApp', []);

function MyCtrl($scope, $q, $timeout) {

    var thenFn = function(value){
        console.log('resolved ', value);
        return value;
    },
    q1 = $scope.q1 = $q.defer(),
    q2 = $scope.q2 = $q.defer(),
    p1 = $scope.q1.promise, 
    p2 = $scope.q2.promise;


    $scope.fromThen = $q.all([
                            p1.then(thenFn), 
                            p2.then(thenFn)
                        ])
                        .then(function(values) {        
                            console.log(values);
                            return values;
                        });

    // Must start the AngularJS digest process
    // to allow $q.resolve() to work properly
    // So use $timeOut() or $apply()

    setTimeout(function () {
        $scope.$apply( function() {            
            console.log('resolving delayed promises');
            q1.resolve({value : 1});
            q2.resolve({value : 2});
        });
    }, 100, this);

    /* 
     *  Alternative approach
     *
    $timeout( function() {
        console.log('resolving delayed promises');
        q1.resolve({value : 1});
        q2.resolve({value : 2});        
    });
    */
}

Here is how you would apply this to your code (haven't tested it, so it's just a direction, but it should get you going):

var promises = [];
for (var i = 0; i < instances.length; i++){

                //$http return a promise, so you can just push it
                promises.push( $http
                .get('/getLongrunners',{params: {envFlag: '',instance: instances[e].instance}}));

}

$q.all(promises).then(function(values){
   //values should contain an array with all the results you got from all the requests, so you can run through it and aggregate the results
});
Sign up to request clarification or add additional context in comments.

7 Comments

I looked into that, and it seemed that would only work when a promise has been returned. AKA: the $http.get() call.. However, I want to be able to do everything contained within the then() call as well. So this would no longer be dealing with a promise. I understand I could do many $http.get() calls and use $q.all() but once the promise is resolved, would $q.all be appropriate?
Okay, that actually makes a lot of sense. I just don't understand the need for the setTimeout() ?
And also, given the looping fashion of my $http.get() calls, the number of promises to be returned is unknown. Is there a way to do the above but allow it to grow if necessary?
I've edited my answer with an example more fitted for your case
so to operate on the individual response data sets, would I just loop through the promises within the $q.all.then()? what would the syntax be to access each individual response?
|
2

Promises are chainable: when you return something inside the success callback of a promise you get a new promise that resolves with the returned value.
Example from angular documentation ("Chaining Promises" part):

promiseB = promiseA.then(function(result) {
    return result + 1;
});

// promiseB will be resolved immediately after promiseA is resolved and its value
// will be the result of promiseA incremented by 1

So, in your /getLongRunners callbacks you can return a value that immediately resolves (like true) so that you get a promise that resolves as soon as the callback is done. If you collect all these "child" promises in an array you can than pass that array to $.all, and it will resolves when all the promises resolve, i.e. as soon as all the callbacks are done.

Here I replace the for loop and embedded immediately-executed function with the forEach method: it's clearer and avoids the closure problem you encountered

var promises = [];
instances.forEach(function(instance, i) {
    promises.push($http
        .get('/getLongrunners', {
            params: {envFlag: '', instance: instances[e].instance}
        })
        .then(function(response) { 
            var longrunners = response.data;
            // whatever you have to do
            return true;
         }, getLongrunnersFail);
 });
 $q.all(promises).then(function() {
     // When you are here, all your callbacks will have been executed
 });

7 Comments

This is phenomenal! I'm going to try and implement this now! Thank you so much for the help.
So I went through and implemented this, and I added a console.log() to tell me when the q.all().then(function(){}) was being called, and it is called a bunch of times while everything else is processing. I was under the impression this would only be called once all were finished. It was called many many times. Any ideas?
Did you pass the promises to .all?
I did, I edited my original post with my current code.
The .all should be outside the forEach function
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.