How to pass extra data down a Parse Promise chain [duplicate]
Asked Answered
N

3

15

In my Parse Cloude code I need to run a few successive queries, each of them using a "find()".

Example:

var promise = firstQuery.get(objectId).then(function(result1){
            return secondQuery.find();
        }).then(function(result2){
            return thirdQuery.find();
        }).then(function(result3) {

             // here I want to use "result1", "result2" and "result3"
        });

The question is: how do I access "result1" and "result2" in the final "then" statement, without assigning them to variables declared in the parent scope.

Why do I ask this: You cannot use the parent scope trick if you are nesting a bunch of promises which you create in a loop in order for them to be executed in parallel (imagine a for loop around the above statement whereby all the promises are put in an array and then evaluated using "Parse.Promise.when". They would all start modifying the parent scope variables at the same time.)

Can I create some kind of promise object where I could return something along the lines of:

Parse.promise({result:result1,findResult:secondQuery.find()};

so I could get the values out of the "result2" parameter by doing

result2.result 

and

result2.findResult

I hope I make myself clear. This is not very easy to explain.

Nephelometer answered 12/8, 2014 at 0:15 Comment(3)
Are you using the results of the first query in the second query? Or could you run all three queries in parallel and just examine the results of all three at the end? If so, there's a faster, easier way to do this.Recitativo
So you mark my question as "being asked before", Bergi, while my question is from August 2014 and your is from January 2015?Nephelometer
@JorisMans: I did mark it as a duplicate, which is totally valid as the other question is the canonical question on this topic (not sure what you mean by "technically they are not correct"). I have to admit that I hadn't seen your question, with its fairly good answers, when creating the canonical one; I just came across yours when using the search. Notice that "asked before" is a known inacurracy and does not mean that the date matters anyhow in a close decision.Declarative
F
25

You can make use of closures to do this, without the need for any extra objects or wrapping.

var promise = firstQuery.get(objectId).then(function(result1){
    return secondQuery.find()
    .then(function(result2) {
        return thirdQuery.find()
        .then(function(result3) {
            //can use result1, result2, result3 here
        });
    });
});

This "nested" syntax is identical in function to your "chaining" syntax.


EDIT based on comments

If your promise chain is long and complex enough that this nested syntax becomes ungainly, the logic is probably complex enough to merit abstraction into its own function.

function complexQuery(objectId) {
    var results = {};
    return firstQuery.get(objectId).then(function(result1) {
        results.result1 = result1;
        return secondQuery.find();
    })
    .then(function(result2) {
        results.result2 = result2;
        return thirdQuery.find();
    })
    .then(function(result3) {
        results.result3 = result3;
        return results;
    });
}

complexQuery(objectId)
.then(function (results) {
    //can use results.result1, results.result2, results.result3
});

Personally, I think that's easier to read and maintain than messing around with .bind.

Flurried answered 12/8, 2014 at 0:24 Comment(7)
The Pyramid of DoomDeplete
I know this works, but when going all crazy with nesting you end up losing one of the big benefits of promises, the fact that you don't need to start nesting them into infinity if you have 10 or more of those calls in succession.Nephelometer
@SecondRikudo That's not as applicable as you might think, except that "highly nested code is ugly and confusing". You can't solve this one simply by explicitly defining named callback functions.Flurried
@JorisMans Personally, having 10 asyncronous calls that need to be run in sequence (can't use "Promise.all"), but don't depend on the state of the previous call or can't carry state from callback to callback, is a sign of a need for a bigger refactor than a clever way of chaining promises.Flurried
e.g. create an object that encapsulates the 10 step chain, and carries the state for the whole process with it.Flurried
One possibility would be that you are looking for objects in 10 different tables in order to delete them, and only delete all of them if there are no errors along the way. Since Parse does not support transactions you can get all kinds of issues if you are deleting objects and halfway through you encounter an error.Nephelometer
Could not for the life of me figure out chained promises until this answer. Saved me a headache. Thanks!Blacken
R
13

You cannot use the parent scope trick

Well, since both the other answers do this, let me suggest a solution that doesn't. You can pass resolved promises along. This also has the added benefit of no nesting or closures.

This is based on the concept that promises are proxies for values already, so you don't actually have to create one long chain:

var firstObject = firstQuery.get(objectId);
var secondObject = firstObject.then(secondQuery.find.bind(secondQuery));
var thirdObject = secondObject.then(thirdQuery.find.bind(thirdQuery));
Promise.all(firstObject, secondObject, thirdObject).then(function(r1, r2, r3){
   // here you can use "r1", "r2" and "r3"
});

In standard promises, rather than parse code this would look similar:

Promise.all([firstObject, secondObject, thirdObject]).then(function(){
   var r1 = arguments[0], r2 = arguments[1], r3 = arguments[2]; 
   // here you can use "r1", "r2" and "r3"
});

With bluebird you can use .spread for a shortcut or .bind or actual context. If you absolutely must create one chain, you can pass context by returning multiple promises using Promise.all but I believe this approach is preferable.

Rickettsia answered 12/8, 2014 at 6:58 Comment(2)
Does this work in Parse Cloud Code?Nephelometer
I would not have posted it this way otherwiseRickettsia
T
1

If you want to keep your flat chaining syntax instead of nesting, you can use an object from the parent scope to share:

var shared = {};
var promise = firstQuery.get(objectId).then(function(result1){
    // save results to shared object
    shared.result1 = result1;
    return secondQuery.find();
}).then(function(result2){
    shared.result2 = result2;
    return thirdQuery.find();
}).then(function(result3) {
    // here I want to use "result1", "result2" and "result3"
    // just use shared.result1, shared.result2
});
Tremblay answered 12/8, 2014 at 2:9 Comment(2)
As stated in the question, I am looking for a solution without the parent scope.Nephelometer
Your issues with the parent scope need clarifying, if "they all modify the parent scope at the same time" is really an issue, then all kinds of shared access would be an issue. Also note that promises doesn't mean multi-threaded, you still only get one executed at a time, Parse.Promise.when() will simply process them all before chaining to the success handler.Tremblay

© 2022 - 2024 — McMap. All rights reserved.