Reputation: 14318
I'm still learning JavaScript Promise
s, and I came across a behavior I don't understand.
var o = $("#output");
var w = function(s) {
o.append(s + "<br />");
}
var p = Promise.resolve().then(function() {
w(0);
}).then(function() {
w(1);
});
p.then(function() {
w(2);
return new Promise(function(r) {
w(3);
r();
}).then(function() {
w(4);
});
}).then(function() {
w(5);
});
p.then(function() {
w(6);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
I would expect these statements to run in order--that is, the that output would be
0
1
2
3
4
5
6
Instead, the output is
0
1
2
3
6
4
5
Even removing the inner Promise
gives, what seems to me to be, contradicting results. 1
is output before 2
, but 6
is output before 5
.
Can someone explain this to me?
Something I have noticed is that reassigning p
each time gives us the order I would expect.
Upvotes: 15
Views: 12640
Reputation: 867
Let's give every promise and function in your example a name for clarity:
let pz = Promise.resolve();
let p0 = pz.then( function f0() { w(0); } );
let p1 = p0.then( function f1() { w(1); } ); // 'p' in your example
let p2 = p1.then(
function f2() {
w(2);
let p3 = new Promise(
function f3( resolve_p3 ) {
w(3);
resolve_p3();
}
);
let p4 = p3.then( function f4() { w(4); } );
return p4;
}
);
let p5 = p2.then( function f5() { w(5); } );
let p6 = p1.then( function f6() { w(6); } );
Let's see what happens step by step. First the top level execution:
pz
is fulfilled, hence pz.then(f0)
immediately queues f0
for execution, the result of which will resolve p0
.f1
is scheduled to be queued once p0
is fulfilled, and the result thereof will resolve p1
.f2
is scheduled to be queued once p1
is fulfilled, and the result thereof will resolve p2
.f5
is scheduled to be queued once p2
is fulfilled, and the result thereof will resolve p5
.f6
is scheduled to be queued once p1
is fulfilled, and the result thereof will resolve p6
.Then the queued jobs (initially just f0
) will be run:
f0
is executed: prints "0". p0
becomes fulfilled, hence f1
is added to the queue.f1
is executed: prints "1". p1
becomes fulfilled, hence f2
and f6
are added to the queue (in that order). This is the crucial bit, since it means f6
will be executed before any jobs queued later.f2
is executed: prints "2".f2
): new Promise
calls f3
, which prints "3" and fulfills p3
.f2
): since p3
is already fulfilled, f4
is added to the queue, the result of which will resolve p4
.f2
finally resolves p2
to p4
, which means that once p4
becomes fulfilled, so will p2
.f6
is executed: prints "6". p6
becomes fulfilled.f4
is executed: prints "4". p4
becomes fulfilled. p2
becomes fulfilled, hence f5
is added to the queue.f5
is executed: prints "5". p5
becomes fulfilled.Upvotes: 3
Reputation: 15810
The reason you see 6
early is because you didn't chain, you branched.
When you call p.then().then().then()
, you've got a chain of promises that must execute in the correct order.
However, if you call p.then().then(); p.then()
, you've got 2 promises attached to p
- essentially creating a branch, and the 2nd branch will execute along with the first.
You can fix this by ensuring you chain them together p = p.then().then(); p.then();
FYI, you almost NEVER want to branch, unless you bring them back together (eg. Promise.all
), or are intentionally creating a "fire and forget" branch.
Upvotes: 35
Reputation: 600
What does r() do?
The ordering is indeterminate because you're thenning on the same promise -> this specifically refers to the second and third chain.
If you were doing the following, then order can be guaranteed:
var p = Promise.resolve().then(function() {
w(0);
}).then(function() {
w(1);
});
// Key difference, continuing the promise chain "correctly".
p = p.then(function() {
w(2);
return new Promise(function(r) {
w(3);
r();
}).then(function() {
w(4);
});
}).then(function() {
w(5);
});
p.then(function() {
w(6);
});
Upvotes: 4