深入理解Promise.all的执行顺序与常见陷阱
本文深入解析Promise.all的执行机制,明确其任务发起顺序与结果映射规则。通过对比实验揭示异步非阻塞特性,并重点排查因使用普通函数导致this混淆引发的undefined返回问题,提供使用箭头函数的解决方案及Promise.race用法。
Promise.all执行顺序解析
异步编程中的Promise机制
Promise.all方法解析
Promise.all接收的Promise数组是顺序执行还是并发执行,返回结果是否保持顺序?
目前存在两种观点:
- 若同步执行则效率低下,如何改为异步执行?
- 部分观点认为结果有序,部分认为无序。
通过源码实现进行解析:
运行环境配置:
vscode 1.20.1
node v8.9.0
npm v5.6.0
测试代码示例:
// 获取随机数,toFixed为四舍五入保留小数,0为保留整数,范围~1000
const getRandom = () => +(Math.random()*1000).toFixed(0);
const asyncTask = (taskID) => new Promise( (resolve) => {
// 随机获取一次0~1000的随机数
let timeout = getRandom();
// 打印出传递进来的ID号 taskID=1 start.
console.log(`taskID=${taskID} start.`);
// 设置计时时间,function()等价于 () => {...}
setTimeout(function() {
// 打印出执行的taskID,和timeout
console.log(`taskID=${taskID} finished in time=${timeout}.`);
// 异步成功执行
resolve(taskID)
}, timeout);
});
Promise.all([asyncTask(1),asyncTask(2),asyncTask(3)])
.then(resultList => {
console.log('results:',resultList);
});
测试结果如下:
首次运行
taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=2 finished in time=321.
taskID=3 finished in time=506.
taskID=1 finished in time=932.
results:
Array(3) [1, 2, 3]
第二次运行
taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=1 finished in time=243.
taskID=3 finished in time=305.
taskID=2 finished in time=792.
results:
Array(3) [1, 2, 3]
第三次运行
taskID=1 start.
taskID=2 start.
taskID=3 start.
taskID=3 finished in time=380.
taskID=1 finished in time=539.
taskID=2 finished in time=782.
results:
Array(3) [1, 2, 3]
基础知识补充:
// toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
NumberObject.toFixed(num)
// num 必需。规定小数的位数,是 0 ~ 20 之间的值,包括 0 和
// 20,有些实现可以支持更大的数值范围。如果省略了该参数,将用 0 代替。
Promise构造函数接收一个执行器函数,该函数包含resolve(成功回调)和reject(失败回调)两个参数。
new Promise(function(resolve, reject) {
setTimeout(() => resolve(5), 0)
}).then(v => console.log(v)) // 5
Promise实例必须通过resolve或reject返回,并使用then()或catch()获取,不能在new Promise内部直接return,否则无法获取返回值。
由此可见,Promise.all中的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]按顺序发起。
尽管结果是异步的,互不阻塞,每个任务完成时间不确定,但所有任务结束后,结果仍按顺序映射到resultList中,与Promise.all中的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]一一对应。
Promise.all()语法与返回机制
深入解析Promise.all()
为何返回的是一个数组?
再次查看这段代码:
Promise.all([asyncTask(1),asyncTask(2),asyncTask(3)])
.then(resultList => {
console.log('results:',resultList);
});
通常异步操作只涉及单个Promise,现在使用all()方法包装多个Promise实例。
语法简洁:仅接收一个参数,即可迭代对象,如数组或Symbol类型。
Promise.all(iterable).then().catch()
传入三个Promise实例:
Promise.all([
new Promise(function(resolve, reject) {
resolve(1)
}),
new Promise(function(resolve, reject) {
resolve(2)
}),
new Promise(function(resolve, reject) {
resolve(3)
})
]).then(arr => {
console.log(arr) // [1, 2, 3]
})
回顾上述过程,是否已明白?
由于传入的是数组,返回结果也必须是数组,并对结果进行映射。
Promise.race()方法对比
Promise.race()方法
语法与all()相同,但返回值不同。race()在多个Promise实例中,只要有一个resolve或reject,即返回该结果,其余实例不再执行。
简单示例中,返回结果为3,因为第三个Promise执行最快。
Promise.race([
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000)
}),
new Promise(function(resolve, reject) {
setTimeout(() => resolve(2), 100)
}),
new Promise(function(resolve, reject) {
setTimeout(() => resolve(3), 10)
})
]).then(value => {
console.log(value) // 3
})
异步函数中this指向问题排查
异步操作中为何使用箭头函数
这是之前的困惑点,对前例进行改造。
示例如下:
const getRandom = () => +(Math.random()*1000).toFixed(0);
function test(taskID) {
new Promise( (resolve) => {
// 随机获取一次0~1000的随机数
let timeout = getRandom();
// 打印出传递进来的ID号
console.log(`taskID=${taskID} start.`);
setTimeout(function() {
console.log(`taskID=${taskID} finished in time=${timeout}.`);
resolve(taskID)
}, timeout);
} )
}
Promise.all([test(1),test(2),test(3)])
.then(resultList => {
console.log('results:',resultList);
});
先观察结果:
第一次测试:
taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=1 finished in time=460.
taskID=2 finished in time=704.
taskID=3 finished in time=883.
第二次测试:
taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=2 finished in time=17.
taskID=3 finished in time=212.
taskID=1 finished in time=612.
第三次测试:
taskID=1 start.
taskID=2 start.
taskID=3 start.
results:
Array(3) [undefined, undefined, undefined]
taskID=3 finished in time=130.
taskID=1 finished in time=256.
taskID=2 finished in time=593.
实验至少需进行3次以上以增强说服力。
通过输出结果发现数组内数据均为undefined,需找出原因,即使用箭头函数的原因。
首先通过调试查找原因
如图所示:

程序首先打印出
taskID=1 start. taskID=2 start. taskID=3 start.
说明先执行了
console.log(`taskID=${taskID} start.`);
因此在此处设置断点逐步调试,如下:

根据上图,console.log(taskID=${taskID} start.)每次均被执行,setTimeout也执行,但3次后直接执行.then(),原因是Promise.all()未等待完整数据即执行.then(),未在resolve前等待。
这表明存在异常,异常源于Promise.all()参数中的函数导致this混淆,故需使用对象,更准确说是实例。
注意:
以这段代码为例:
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results); // [1, 2, 3]
});
上述方法中,当promise数组中所有实例均resolve时,该方法才返回,并将结果传递至results数组。若任一promise为reject,则Promise.all立即终止,返回新的reject Promise对象。reject使用示例如下:
var p1 = Promise.resolve(1),
p2 = Promise.reject(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
//then方法不会被执行
console.log(results);
}).catch(function (e){
//catch方法将会被执行,输出结果为:2
console.log(2);
});
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
