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?
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.
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.
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 ).
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:
p
variable, finds the .then()
method on it and calls it..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..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.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.
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 eitheronFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
2.3.3 Ifx
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:
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/rejectedthen
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 discardedasync()
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With