先记住这个答案
基本是按完成顺序。一个 Promise 兑现时,它身上登记过的 then 回调会被放进微任务队列,所以先兑现的先执行,跟 Promise 是先创建还是后创建、then 是先写还是后写都没关系。但有一个常见例外:用一个 Promise 去 resolve 另一个 Promise,外层要多等两个微任务才兑现,看起来就像「先完成的反而后执行」。另外 Promise.all 返回的数组永远按输入顺序排列,不反映谁先完成。
- 回调在 Promise 兑现那一刻才进微任务队列
- 同一轮兑现的,按进队顺序执行
resolve一个 Promise 会晚两个微任务兑现Promise.all结果按输入顺序,不按完成顺序
三个定时器看完成顺序
下面三个 Promise 按 a、b、c 的顺序创建,then 也按这个顺序注册,但定时器的时间不一样。
const delay = (name, ms) => new Promise((r) => setTimeout(() => r(name), ms))
const a = delay('a', 30)
const b = delay('b', 10)
const c = delay('c', 20)
a.then((v) => console.log('完成', v))
b.then((v) => console.log('完成', v))
c.then((v) => console.log('完成', v))
Promise.all([a, b, c]).then((list) => console.log('all', JSON.stringify(list)))查看输出与解释
完成 b
完成 c
完成 a
all ["a","b","c"]then 只是在 Promise 上登记一个回调,并不会让它排队。b 在 10 毫秒时兑现,这一刻它的回调才进入微任务队列并马上执行,所以 b 最先打印。a 最晚兑现,排在最后。
Promise.all 等三个都兑现后才执行,结果数组是 ["a","b","c"],按传入的位置放,不是按完成时间放。所以要拿到有序结果,用 Promise.all 就够了,不用自己排序。
例外:用 Promise 去 resolve 会晚两拍
同一轮同步代码里兑现的多个 Promise,理论上按兑现的先后执行。但「兑现」这个时间点有时比你以为的晚。
const p1 = new Promise((r) => r(Promise.resolve('p1'))) // 参数是 Promise
const p2 = Promise.resolve('p2') // 参数是普通值
p1.then((v) => console.log(v))
p2.then((v) => console.log(v))查看输出与解释
p2
p1p2 立刻就兑现了。p1 的 resolve 收到的是一个 Promise,它得先调用这个 Promise 的 then 来展开(第一个微任务),展开后的结果再通过回调传回来(第二个微任务),p1 才真正兑现。这一来一回,p1 的回调就排到了 p2 后面。
async 函数里 return 一个 Promise 也是同样的道理,所以 async () => somePromise 和 async () => 1 放在一起跑,前者的结果会晚出来。
- 已经兑现的 Promise 再调
then - 如果 Promise 早就兑现了,这时调用
then,回调会在调用then的那一刻进队列。所以对一堆已经兑现的 Promise 来说,执行顺序就等于then的调用顺序。这也是题目里「按调用顺序」那种说法的来源,它只在这个前提下成立。
容易答错的地方
- 说
then回调按注册顺序执行 - 只有同一个 Promise 上的多个回调才按注册顺序执行。不同 Promise 之间看的是谁先兑现,注册顺序不起作用。上面三个定时器的例子就是反例。
- 说
Promise.all的结果按完成顺序排 Promise.all内部给每一项记了下标,某一项兑现时把值写到对应下标上,所以结果顺序和输入数组一致。想按完成顺序处理,要自己给每个 Promise 挂then,或者用Promise.race反复取最快的那个。- 在业务里依赖请求返回顺序
- 两个接口先发的不一定先回。比如搜索框连续输入,旧请求慢了就会覆盖新结果。这种问题要靠取消旧请求或者比对请求序号来解决,而不是指望
then的顺序。
面试官还会怎么问?
想让并发请求按完成顺序逐个渲染,怎么写?
给每个请求单独挂 .then(render) 就行,谁先回谁先渲染。如果还要在全部完成后做收尾,再用 Promise.allSettled 等所有请求结束,这样某个请求失败也不影响其它结果展示。
setTimeout 回调和 then 回调谁先执行?
then 回调是微任务,setTimeout 回调是宏任务。每执行完一个宏任务,引擎会先清空微任务队列,再取下一个宏任务。所以同一轮里注册的,then 总是先于 setTimeout 执行。
同一个 Promise 上挂了两个 then,执行顺序确定吗?
确定,按注册顺序执行。Promise 内部维护一个回调列表,兑现时按列表顺序依次放进微任务队列。这个顺序是规范保证的,可以依赖。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。