深入理解Promise.all的执行顺序与常见陷阱

作者:小括号

时间:2026-10-22

浏览:410

本文深入解析Promise.all的执行机制,明确其任务发起顺序与结果映射规则。通过对比实验揭示异步非阻塞特性,并重点排查因使用普通函数导致this混淆引发的undefined返回问题,提供使用箭头函数的解决方案及Promise.race用法。

文章目录

Promise.all执行顺序解析

异步编程中的Promise机制

Promise.all方法解析

Promise.all接收的Promise数组是顺序执行还是并发执行,返回结果是否保持顺序?

目前存在两种观点:

  1. 若同步执行则效率低下,如何改为异步执行?
  2. 部分观点认为结果有序,部分认为无序。

通过源码实现进行解析:

运行环境配置:


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,需找出原因,即使用箭头函数的原因。

首先通过调试查找原因

如图所示:

Promise.all执行顺序解析

程序首先打印出

taskID=1 start. taskID=2 start. taskID=3 start.

说明先执行了


console.log(`taskID=${taskID} start.`);

因此在此处设置断点逐步调试,如下:

Promise.all执行顺序解析

根据上图,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);
});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章