Understanding JavaScript Promises; Stacks and Chaining

Understanding javascript promises; stacks and chaining

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.

How promises and promise chaining works? [Problem with code]

When you have one Promise, you can chain any number of Promises onto its .then. For example

const p = Promise.resolve();
p.then(() => console.log('then 1');
p.then(() => console.log('then 2');

means that p has two Promises that branch from it when it resolves: 1 and 2 (in addition to the promise p itself).

  p
/ \
/ \
1 2

What you're doing in your first code

let p = new Promise((resolve, reject) => {
resolve("Hello world")
});
p.then(data => {
console.log("second");
}).then(data => {
console.log("third")
})
return p;

is like

"Hello world" = <Promise you return>
|
|
|
second
|
|
|
third = <unused Promise expression that the then chain resolves to>

You have two branches: the Promise you're returning resolves when Hello world runs, not when third runs.

On the other hand, when you call .then multiple times on a Promise, the whole expression evaluates to the Promise that resolves when the final .then runs:

let p = new Promise((resolve, reject) => {
resolve("Hello world")
}).then(data => {
console.log("Hello world a second time!");
}).then(data => {
console.log("Hello world a third time")
})

return p;

is like

"Hello world"
|
|
'Hello second'
|
|
'Hello third' = <Promise you return>

where the returned Promise is the one that resolves right after Hello third runs.

Promise chaining and all

The Promise.all don't manage the chaining?

Yes it did. It managed all the promises in the array, which all were fulfilled, that's why it logged all clear.

The Promise.all does not know - cannot know - about that new promise you created with thePromise.then(…). It's not in the chain, it's branching away. And when it is rejected, nothing handles the error.

Chaining Javascript promises

First, a little background on how the relevant parts of promises work:

p1.then(...) does return a new promise that is chained to the previous one. So, p1.then(...).then(...) will execute the second .then() handler only after the first one has finished. And, if the first .then() handler returns an unfulfilled promise, then it will wait for that returned promise to resolve before resolving this second promise and calling that second .then() handler.

Secondly, when a promise chain rejects anywhere in the chain, it immediately skips down the chain (skipping any fulfilled handlers) until it gets to the first reject handler (whether that comes from a .catch() of from the second argument to .then()). This is a very important part of promise rejection because it means that you do not have to catch rejections at every level of the promise chain. You can put one .catch() at the end of the chain and any rejection that happens anywhere in the chain will go directly to that .catch().

Also, it's worth understanding that .catch(fn) is just a shortcut for .then(null, fn). It works no differently.

Also, keep in mind that (just like .then()) a .catch() will also return a new promise. Any if your .catch() handler does not itself throw or return a rejected promise, then the rejection will be considered "handled" and the returned promise will resolve, allowing the chain to continue on from there. This allows you to handle an error and then consciously decide if you want the chain to continue with normal fulfill logic or stay rejected.

Now, for your specific questions...

If so, then wouldn't that mean that the catch callback would be called
only if the promise returned from p1.then, not the promise p1,
resolved to rejected? And wouldn't I have to do this:

No. Rejections propagate immediately down the chain to the next reject handler, skipping all resolve handlers. So, it will skip down the chain to the next .catch() in your example.

This is one of the things that makes error handling so much simpler with promises. You can put .catch() at the end of the chain and it will catch errors from anywhere in the chain.

There are sometimes reasons to intercept errors in the middle of the chain (if you want to branch and change logic on an error and then keep going with some other code) or if you want to "handle" the error and keep going. But, if your chain is all or nothing, then you can just put one .catch() at the end of the chain to catch all errors.

It is meant to be analogous to try/catch blocks in synchronous code. Putting a .catch() at the end of the chain is like putting one try/catch block at the highest level around a bunch of synchronous code. It will catch exceptions anywhere in the code.

All three work. I can understand the latter two. The gist of my
question is, why does the first approach also work?

All three are pretty much the same. 2 and 3 are identical. In fact, .catch(fn) is nothing more than a shortcut for .then(null, fn).

Option 1 is slightly different because if the onFulfilled handler throws or returns a rejected promise, then the .catch() handler will get called. In the other two options, that will not be the case. Other than that one difference, it will work the same (as you observed).

Option 1 works because rejections propagate down the chain. So, if either p1 rejects or if the onFulfilled handler returns a rejected promise or throws, then the .catch() handler will be called.

JavaScript Promises: Chaining promise confusion

This is because you return mainPromise in getHerosSlowlyUnchained. Every call to then or similar methods returns a new promise in the chain.

In getHeroChain, your chain is: setTimeout -> setTimeout resolved-> final then.

In getHeroUnChain your chain is: setTimeout -> [setTimeout resolved, final then].

Note that in the second example, both setTimeout resolved and final then are after setTimeout. This means that both are given 2.

To fix, either do

return mainPromise.then(function(value) {

console.log(" 2) setTimeout resolved");
console.log(" 2) inside 1st 'then' with value : " + value);
return getHeros(); //return promise

});

or

mainPromise = mainPromise.then(function(value) {

console.log(" 2) setTimeout resolved");
console.log(" 2) inside 1st 'then' with value : " + value);
return getHeros(); //return promise

});
return mainPromise;

Understanding promise chains

The .then method accepts two arguments: onFulfilled and onRejected. If you do not supply an onRejected callback, this particular .then will not be invoked and the chain will fall through to the next handler that implements onRejected or .catch.

Promise.reject()    .then(() => console.log('onFulfilled 1'))    .catch(() => console.log('catch 1'));  // will get called
Promise.reject() .then( () => console.log('onFulfilled 2'), () => console.log('onRejected 2') // will get called ) .catch(() => console.log('catch 2')); // will not get called

Understanding what triggers moving to the next .then() of a javascript promise chain

Think about it this way

fistAsyncFunction()
.then(() => {
secondAsyncFunction()
return undefined
})
.then((iAmUndefined) => {
thirdAsyncFunction()
return undefined
})

^ This is your first example with explicit returns.

It seems like even though the second async function didn't return anything, the program waited for it to return undefined before moving onto the next promise.

So you are sort of right on this one. The second async function does not return anything; that is correct. The program, however, does not wait for secondAsyncFunction() to return undefined. Moreover, it does not wait for secondAsyncFunction() at all. Instead, your program is saying

Okay secondAsyncFunction I will now call you and let you run away; I will not wait for you. Instead I'm just going to return undefined and move on. Hello thirdAsyncFunction!

You are correct in noticing that your thirdAsyncFunction is started before your secondAsyncFunction returned.

Next, you made an amendment to your code to achieve your expected behavior

fistAsyncFunction()
.then(() => {
return secondAsyncFunction()
})
.then(resultOfSecondAsyncFunction => {
thirdAsyncFunction()
return undefined
})

^ this is your second example with explicit returns

Your program is now working the way you want it to because by returning secondAsyncFunction(), you are now instructing your program to wait for the return of that function.

what is going on under the hood that prompts javascript to start executing the next promise?

Under the hood, there is a special function resolve that is used in the Promise constructor. The next promise will start executing when the resolve function of the previous promise is called. Of course, both promises must be tied together by a .then.



Related Topics



Leave a reply



Submit