问题
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?
回答1:
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:
- The interpreter takes your
p
variable, finds the.then()
method on it and calls it. - 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. - 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. - Then sometime in the future, the original promise
p
gets resolved by its own async operation. When it gets resolved, it then calls anyresolve
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.
回答2:
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:
- #1: you have 7 promises (
async
call plus 4then
's, plus two fromasync3()
/async4
),serverSidePromiseChain
will point to the last promise returned bythen
. Now if the promise returned byasync()
is never resolved/rejected, thenserverSidePromiseChain
will also be in the same situation. Same withasync3()
/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 byasync()
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 withtrue
, the second callback will to the same, the third one will have the chance to convert the chain to a failed one, ifasync3()
's promise gets rejected, same with the callback that returnsasync4()
'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.
回答3:
Implementation 1 and 3 appear to be equivalent.
In implementation 2, the last 3 .then()
functions all act on the same promise. The .then()
method returns a new promise. A fulfilled promise's value cannot be changed. See Promises/A+ 2.1.2.2. Your comment in implementation 2, that response is expected to be true, indicates that you expect otherwise. No, response
will not be true (unless that was the value from the original promise).
Let's just try it out. Run the following code snippet to see the differences:
function async(){ return Promise.resolve("async"); }
function async3(){ return Promise.resolve("async3"); }
function async4(){ return Promise.resolve("async4"); }
function implementation1() {
logContainer = document.body.appendChild(document.createElement("div"));
console.log("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();
});
}
function implementation2() {
logContainer = document.body.appendChild(document.createElement("div"));
console.log("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();
});
}
function implementation3() {
logContainer = document.body.appendChild(document.createElement("div"));
console.log("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();
});
}
var logContainer;
var console = {
log: function() {
logContainer.appendChild(document.createElement("div")).textContent = [].join.call(arguments, ", ");
}
};
onload = function(){
implementation1();
setTimeout(implementation2, 10);
setTimeout(implementation3, 20);
}
body > div {
float: left;
font-family: sans-serif;
border: 1px solid #ddd;
margin: 4px;
padding: 4px;
border-radius: 2px;
}
来源:https://stackoverflow.com/questions/29853578/understanding-javascript-promises-stacks-and-chaining