Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing a previously fulfilled promise result in a promises chain [duplicate]

What is the correct pattern, when coding with promises, to access data coming from long before in a chain of promises?

For example:

do_A.then(do_B).then(do_C).then(do_D).then(do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_IsSuccesfullyCompleted)

My current solution: passing along a single JSON structure through the chain, and let each step populate it. Any opinion about that?

like image 287
lOlive Avatar asked Aug 19 '13 16:08

lOlive


2 Answers

I don't think there's one "correct" pattern for this. Your solution sounds neat, however, it's a bit tightly coupled. It may work great for your situation, but I see a few problems with it as a general pattern:

  1. Participating steps need to agree on the structure of the collector object.

  2. Every step needs to participate in at least the forwarding of the object, which can get tedious if the chain is long and the need for previous data occurs sporadically. This is also inflexible to insertion of steps not written by you (chaining doesn't always happen linearly).

  3. Said differently: Unless do_A|B|C|D and do_E are under your control, you'll need to wrap them with boilerplate to store off your collector object in a closure and convert to and from the functions' natural inputs and results, since the functions wont be "in on" your pattern.

  4. On the other hand, if the functions are in on it, then the data-dependencies between the steps have effectively become hidden inside the functions. This may look clean, but it could become a maintenance problem. For example: if this is a team project, then someone might think they can re-order your steps, absent any no clue in the call-pattern what inputs do_E requires.

I would suggest a more straightforward approach using closures:

var a, c;

do_A()
.then(function(result) { a = result; return do_B(); })
.then(do_C)
.then(function(result) { c = result; return do_D(); })
.then(function() {
   return do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_Succeeds(a, c);
})
.catch(failed);

There's no collector object to define; do_A|B|C|D and do_E can be generic functions without knowledge of any pattern; there's no boilerplate unless returned data is relied on (do_B and do_D); and the data-dependencies (a and c) are explicit.

like image 59
jib Avatar answered Oct 19 '22 23:10

jib


Yes, this is the correct way to chain state with actions.

Chaining .then statements is very common and is usually our building block when piping things around. It's at the very core of promises.

What you're doing is both correct and idiomatic.


For the curious spirit let's show this.

In order to verify this - we can check the promises specification.

We want to verify that:

  1. It chains
  2. In the case of a rejection, it doesn't call the handler in the chained then
  3. It rejects the next promise returned from then with the same reason
  4. It executes in sequence passing return value.

Let's verify these in order, using the specification - in particular .then:

1. It chains

7.1 then must return a promise [3.3].

Great, let's verify that it also chains on fullfillment

If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure >[[Resolve]](promise2, x).

Great, so we know that when our promise resolves or rejects then our then handler is called with the appropriate parameter. So .then(do_A).then(do_B) will always work assuming do_A resolves.

2. In the case of a rejection, it doesn't call the handler in the chained then

7.iv. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.

Great, so it rejects and calls onRejected if it's there, if it doesn't it chains.

3. It rejects the next promise returned from then with the same reason

We just covered that in 2.

4. It executes in sequence passing return value.

That is again

If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

So, if you set onFulfilled it'll run the resolution process. The resolution process itself dictates:

The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.

If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).

Where y is the return value of x.

Great! so it works.

like image 23
Benjamin Gruenbaum Avatar answered Oct 19 '22 23:10

Benjamin Gruenbaum