Cereal Killer
Cereal Killer

Reputation: 3414

Break a for loop with promises

In my node js App, I have a function that checks if you have the permissions:

//queryPermissions is an object that contains all possible permissions:
//each property of queryPermission is an object containing the values to be checked
for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method));
    }
}

Q.all(promisesArray).then(function(response) {
    response.forEach(function(value) {
        //response is an array with all promises' resolved values
        //if all values are true -> access granted
        //if one or more values are false -> access denied
    }
}

This works good, but if only one of the values returned by checkThis inside the for loop is false, than the result would be access denied; so it is not efficient to continue to check other permissions after the first one that resoves with false; CheckThis returns a promise but sometimes it needs to wait for a query result, sometimes it resolves immediately, it depends.

Is there a way I can break the loop (if it is not finished yet) when the first "checkThis" returns false?

Upvotes: 2

Views: 3166

Answers (4)

Bergi
Bergi

Reputation: 664610

Not with any native Q function. You could however write an every method for promises yourself (loosely based on Q.all):

Q.every = function every(promises) {
    return Q.when(promises, function (promises) {
        var countDown = 0;
        var deferred = defer();
        for (var i=0; i<promises.length; i++) {
            var promise = promises[i];
            var snapshot;
            if (
                Q.isPromise(promise) &&
                (snapshot = promise.inspect()).state === "fulfilled"
            ) {
                if (!snapshot.value) {
                    deferred.resolve(false);
                    return deferred.promise;
                }
            } else {
                ++countDown;
                q.when(
                    promise,
                    function (value) {
                        if (!value)
                            deferred.resolve(false);
                        else if (--countDown === 0)
                            deferred.resolve(true);
                    },
                    deferred.reject,
                    (function(index) {
                        return function (progress) {
                            deferred.notify({ index: index, value: progress });
                        };
                    }(i));
                );
            }
        }
        if (countDown === 0) {
            deferred.resolve(true);
        }
        return deferred.promise;
    });
}

Upvotes: 2

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276306

Here is an alternative solution to Bergi's approach - we map false return values to the exceptional condition failures they are and use Q.all directly:

Your current code does:

for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method));
    }
}

We add an additional step:

for (var key in queryPermissions) {
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method).then(function(val){
             if(!val) throw new Error("Invalid Permissions");
             return true;
        });
    }
}

This simple addition would let us use Q directly:

Q.all(promisesArray).catch(function(err){
     // one or more authentication errors
}).then(function(){
      // everyone validated, all ok user authenticated here
});

This is a more general method - using the exception pipeline for exceptional cases can greatly simplify your code.

Upvotes: 2

Cereal Killer
Cereal Killer

Reputation: 3414

Maybe I had a sudden enlightment but was not so hard: just using a global flag:

var alreadyFailed = false;

//queryPermissions is an object that contains all possible permissions:
//each property of queryPermission is an object containing the values to be checked
for (var key in queryPermissions) {
    if (alreadyFailed) {
        break;
    }
    if (queryPermissions.hasOwnProperty(key)) {
        promisesArray.push(checkThis(key, req.method));
    }
}

Then it is sufficient to set alreadyFailed to true inside checkThis when a false result occurs; of course it can happen that the for loop ends before the first resolve to false, but in this case there's no way to do better; but if checkThis resolves with a false before the for loop ends -> it will be stopped

Upvotes: 0

Vinz243
Vinz243

Reputation: 9938

It's not possible. Indeed, Q.all run parallel promises, so when you want to break, the other promises are still running. So they can't be stopped. One way is to use Q.spread but therefore the promises are no more parallel :(.

Upvotes: 0

Related Questions