4

I'm using straight ES6 Promises (with the es6-promise polyfill library) and I'm running into a problem with accessing results from previous promises in chained ones.

This problem is identical in the context of Angular/Q, but I'm dissatisfied with the answer and wanted to see if there's a better way:

How to access result from the previous promise in AngularJS promise chain?

Consider the code snippet below:

Student.find().then(function(student) {
        return HelpRequest.findByStudent(student);
    }, function(error) { //... }
).then(function(helpRequest) {
    // do things with helpRequest...
    // PROBLEM: I still want access to student. How can I get access to it?
});

In the chained promise, I want to use the student object that I got in the first promise. But as written, this can't access it. I have a couple apparent options:

  1. store the student in a variable in an outer scope (yuck)
  2. I actually don't know how this would work, but the solutions in the other question suggest I can call then on the result of HelpRequest.findByStudent() and Promise.resolve the combined result inside the Student.find().then call. The below implementation won't work I think, though.

    Student.find().then(function(student) {
            var data = {student: student};
            HelpRequest.findByStudent(student).then(function(helpRequest) {
                data.helpRequest = helpRequest;
            });
            // PROBLEM: if HelpRequest.findByStudent(student) is asynchronous, how 
            // does this get the data before returning?
            return data; 
        }, function(error) { //... }
    ).then(function(helpRequest) {
        // do things with helpRequest and student
    });
    

I absolutely don't want to do the processing of the helpRequest nested inside of the Student.find() method, as that defeats the purpose of chaining Promises; and even if the second example can be worked into a usable state, it still feels like a hack.

Is there a better way to do achieve this without having to introduce global state or nesting into my code? For instance, is there a way to call Promise.resolve() on multiple values, some of which may be promises and some of which are not?

I'm curious, hope I have alternatives/can understand how to make this work properly without introducing nesting or state!

Community
  • 1
  • 1
osdiab
  • 1,972
  • 3
  • 26
  • 37
  • Can you not just return the student along with the help request in the second promise? `return { helpRequest: return HelpRequest.findByStudent(student), student }` – Steven Wexler Nov 13 '14 at 07:01
  • I've tried to find a dupe by couldn't but I'm sure I've answered this before, it might have been on the bluebird issue tracker so there's that. If someone finds the dupe let me know. – Benjamin Gruenbaum Nov 13 '14 at 07:56

1 Answers1

6

In my opinion, the zen of promises is all about figuring out they're really just asynchronous values. If you start using them as such these problems become simpler in many cases. It's not a silver bullet but it sure does help:

In ES5:

var student = Student.find();
var helpRequest = student.then(HelpRequest.findByStudent);
Promise.all([student, helpRequest]).then(function(results){
    var student = results[0];
    var helpRequest = results[1];
    // access both here
});

In ES6, with all its features:

var student = Student.find();
var helpRequest = student.then(HelpRequest.findByStudent);
Promise.all([student, helpRequest]).then(([student, helpRequest]) => {
    // access both here
});

In another richer promise library (bluebird):

var student = Student.find();
var helpRequest = student.then(HelpRequest.findByStudent);
Promise.join(student, helpRequest, function(student, helpRequest){
    // access both here
});
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • So in this example, would the `Student.find()` promise get executed twice? Once for the `HelpRequest.findByStudent()` result, and once for the `Promise.all` method? It's launching off an HTTP request, so that might be a bit less than ideal. But perhaps I'm misunderstanding, as this does look quite elegant! – osdiab Nov 13 '14 at 08:05
  • 2
    @OmarDiab A promise represents a _proxy for a value_, the value is already there. Chaining to a promise does not launch any action on itself - it will make a single http request. The `find` method launches the request - the `then` handlers only let us hook on its result when it is done. Calling `.then` on `student` twice is no different from passing a variable to two different functions - it will not execute the code that assigned to that variable to begin with. – Benjamin Gruenbaum Nov 13 '14 at 08:08
  • Ohhhhh that makes much more sense. My mental model of promises was off. Fantastic, that definitely clears things up :-) – osdiab Nov 13 '14 at 09:50