JavaScript Promise then() 的顺序问题

15

我还在学习JavaScript的Promise,但我遇到了一种我不理解的行为。

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>

我希望这些语句按顺序执行,也就是说,输出应该是

0
1
2
3
4
5
6

相反,输出结果为

0
1
2
3
6
4
5

即使删除内部的Promise,我认为仍然存在矛盾的结果。 12之前输出,但65之前输出。

有人能解释一下吗?

我注意到的一件事是每次重新分配p都会给我们带来我期望的顺序。


为什么您希望按照特定的顺序编写代码呢?代码执行的顺序不一定与 Promise resolve 的顺序相同;只有 then 方法才能控制 Promise 的执行顺序。 - elclanrs
因为我期望then()按顺序发生。 - dx_over_dt
1
是的,每个 Promise 链的 then - elclanrs
1
我有点困惑。这些链是如何创建/执行的? - dx_over_dt
@elclanrs 我还是有点困惑...根据被接受的答案,我会预期6在3之前触发? - sahbeewah
@sahbeewah new Promise 直接调用您传递的函数作为参数,因此在2到3之间不可能执行任何排队作业。 - Matthijs
3个回答

33

你之所以在早期看到了 6,是因为你没有链式调用,而是进行了分支。

当你调用 p.then().then().then() 时,你拥有了一系列的 Promise 必须按正确的顺序执行。而如果你调用 p.then().then(); p.then(),你就创建了两个 Promise 并附加在 p 上,从本质上讲,这相当于创建了一个分支,第二个分支将与第一个分支同时执行。

你可以通过确保将它们链接在一起来修复这个问题 p = p.then().then(); p.then();

值得注意的是,除非你将它们重新合并(例如使用 Promise.all),或者故意创建一个“点火和忘记”分支,否则几乎永远不要进行分支。


4

r()函数是什么?

由于你在同一个promise上进行了then操作,所以顺序是不确定的,特别是针对第二个和第三个链。

如果你按照以下方式操作,则可以保证顺序:

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);
});

r()只是内部Promise的resolve()函数。所以我的理解是,var p = Promise.resolve(); p.then(f1); p.then(f2);意味着f1()f2()将按照JS引擎的任意顺序排序? - dx_over_dt
1
@dfoverdx:是的,p.then(f); p.then(g)不同于p.then(f).then(g) - elclanrs
好的,我明白了。谢谢! - dx_over_dt
哦,我没看到r是第一个参数(我通常会自己输入resolve、reject来创建Promise)。 - irysius
1
排序不是不确定的,在规范中它是精确定义的。所有已解析的 Promise 的 then() 处理程序将按照 Promise 上的 .then() 调用的顺序排队执行。 - Matthijs
1
@Matthijs 似乎是正确的,请参见 https://dev59.com/Z5_ha4cB1Zd3GeqPxFdJ。 - Elliott Beach

2

为了更加清晰,让我们给你的例子中的每个 Promise 和函数都取一个名字:


Original Answer翻译成"最初的回答"。
var pz = Promise.resolve();
function f0() { w(0); }
var p0 = pz.then(f0);
function f1() { w(1); }
var p1 = p0.then(f1);  // p1 is 'p' in your example

function f2() {
    w(2);
    function f3(resolve_p3) {
        w(3);
        resolve_p3();
    }
    var p3 = new Promise(f3);
    function f4() { w(4); }
    var p4 = p3.then(f4);
    return p4;
}
var p2 = p1.then(f2);
function f5() { w(5); }
var p5 = p2.then(f5);

function f6() { w(6); }
var p6 = p1.then(f6);

让我们逐步看看发生了什么。首先是顶层执行:
- `pz` 被满足,因此 `pz.then(f0)` 立即将 `f0` 排队等待执行,其结果将解析为 `p0`。 - `f1` 被安排在 `p0` 被满足后排队,其结果将解析为 `p1`。 - `f2` 被安排在 `p1` 被满足后排队,其结果将解析为 `p2`。 - `f5` 被安排在 `p2` 被满足后排队,其结果将解析为 `p5`。 - `f6` 被安排在 `p1` 被满足后排队,其结果将解析为 `p6`。
然后运行排队的作业(最初只有 `f0`):
- 执行 `f0`:打印 "0"。`p0` 变为已满足状态,因此将 `f1` 添加到队列中。 - 执行 `f1`:打印 "1"。`p1` 变为已满足状态,因此将 `f2` 和 `f6` 按照顺序添加到队列中。这是关键的一步,因为它意味着 `f6` 将在任何后来排队的作业之前被执行。 - 执行 `f2`:打印 "2"。 - (在 `f2` 中):`new Promise` 调用 `f3`,打印 "3" 并将 `p3` 变为已满足状态。 - (在 `f2` 中):由于 `p3` 已经被满足,因此将 `f4` 添加到队列中,其结果将解析为 `p4`。 - 最后,`f2` 将 `p2` 解析为 `p4`,这意味着一旦 `p4` 被满足,`p2` 也将被满足。 - 执行 `f6`:打印 "6"。`p6` 变为已满足状态。 - 执行 `f4`:打印 "4"。`p4` 变为已满足状态。`p2` 变为已满足状态,因此将 `f5` 添加到队列中。 - 执行 `f5`:打印 "5"。`p5` 变为已满足状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接