Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding javascript promises; stacks and chaining

I've been running into a couple of problems with javascript promises, particularly with stacked chains.

Can anyone explain to me the difference (if there is any!) between these different implementations?

IMPLEMENTATION 1

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
}).then(function(response) {
    console.log('2', response);
    return true;
}).then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
}).then(function(response) {
    console.log('4', response);
    return async4();
})

IMPLEMENTATION 2

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

IMPLEMENTATION 3

var serverSidePromiseChain;
serverSidePromiseChain = async().then(function(response) {
    console.log('1', response);
    return response;
});

serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('2', response);
    return true;
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('3', response); // response expected to be 'true'
    return async3();
})
serverSidePromiseChain = serverSidePromiseChain.then(function(response) {
    console.log('4', response);
    return async4();
})

Does the fact that part of the chain returns a value ('true' in step 2) change the behavior? Do promises require all returned values to be async promises to keep behavior?

like image 494
Federico Avatar asked Apr 24 '15 17:04

Federico


People also ask

What is promise chaining in JavaScript?

Promise chaining: Promise chaining is a syntax that allows you to chain together multiple asynchronous tasks in a specific order. This is great for complex code where one asynchronous task needs to be performed after the completion of a different asynchronous task.

What are the 3 states of a JavaScript promise?

A Promise is in one of these states: pending: initial state, neither fulfilled nor rejected. fulfilled: meaning that the operation was completed successfully. rejected: meaning that the operation failed.

Can we chain promises in JavaScript?

Introduction to the JavaScript promise chaining Note that the setTimeout() function simulates an asynchronous operation. The callback passed to the then() method executes once the promise is resolved. In the callback, we show the result of the promise and return a new value multiplied by two ( result*2 ).


2 Answers

You are illustrating the different between chaining and branching. Chaining wil sequence multiple async operations so one starts when the prior one finishes and you can chain an arbitrary number of items to sequence one after the other.

Branching hooks up multiple async operations to all be in flight at the same time when one trigger operation completes.

Implementations 1 and 3 are the same. They are chained. Implementation 3 just uses a temporary variable to chain, whereas implementation 1 just uses the return value from .then() directly. No difference in execution. These .then() handlers will be called in serial fashion.

Implementation 2 is different. It is branched, not chained. Because all subsequent .then() handlers are attached to the exact same serverSidePromiseChain promise, they all wait only for the first promise to be resolved and then all subsequent async operations are all in flight at the same time (not serially as in the other two options).


It may be helpful in understand this to dive one level down into how this works with promises.

When you do (scenarios 1 and 3):

p.then(...).then(...)

What happens is as follows:

  1. The interpreter takes your p variable, finds the .then() method on it and calls it.
  2. The .then() method just stores the callback it was passed and then returns a new promise object. It does not call its callback at this time. This new promise object is tied to both the original promise object and to the callback that it stored. It will not resolve until both are satisfied.
  3. Then the second .then() handler on that newly returned promise is called. Again, the .then() handler on that promise just stores the .then() callbacks and they are not yet executed.
  4. Then sometime in the future, the original promise p gets resolved by its own async operation. When it gets resolved, it then calls any resolve handlers that it stores. One of those handlers will be the callback to the first .then() handler in the above chain. If that callback runs to completion and returns either nothing or a static value (e.g. doesn't return a promise itself), then it will resolve the promise that it created to return after .then() was first called. When that promise is resolved, it will then call the resolve handlers installed by the second .then() handler above and so on.

When you do this (scenario 2):

p.then();
p.then();

The one promise p here has stored resolve handlers from both the .then() calls. When that original promise p is resolved, it will call both of the .then() handlers. If the .then() handlers themselves contain async code and return promises, these two async operations will be in flight at the same time (parallel-like behavior), not in sequence as in scenario 1 and 3.

like image 156
jfriend00 Avatar answered Nov 04 '22 22:11

jfriend00


Implementation #1 and #3 are equivalent. Implementation #2 differs, as there's no chain there, and all callbacks will be executed on the same promise.

Now let's discuss a little bit about promise chains. The specs tell that:

2.2.7 then must return a promise
2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x)
2.3.3 If x is a promise, adopt its state

Basically calling then on a promise returns another promise, which gets resolved/rejected based on the callback return value. In your case you are returning scalar values, which are then propagated down the chain to the next promise.

In your particular case, here's what happens:

  • #1: you have 7 promises (async call plus 4 then's, plus two from async3()/async4), serverSidePromiseChain will point to the last promise returned by then. Now if the promise returned by async() is never resolved/rejected, then serverSidePromiseChain will also be in the same situation. Same with async3()/async4() if that promise is also not resolved/rejected
  • #2: then is called multiple times on the same promise, additional promises are created however they don't affect the flow of the application. Once the promise returned by async() will be resolved, all the callbacks will be executed, what the callbacks return will be discarded
  • #3: this is equivalent to #1 only now you explicitly pass along the created promises. When the promise returned async() gets resolved, the first callback will be executed, which resolves the next promise with true, the second callback will to the same, the third one will have the chance to convert the chain to a failed one, if async3()'s promise gets rejected, same with the callback that returns async4()'s promise.

Promise chains are best suitable for actual async operations where the operations depend on the results of the previous one, and you don't want to write a lot of glue code, and you also don't want to reach to callback hell.

I wrote a series of articles on my blog about promises, one describing the chaining feature of promises can be found here; the article is targeted for ObjectiveC, however the principles are the same.

like image 28
Cristik Avatar answered Nov 04 '22 20:11

Cristik