Promise.all和Promise.race的区别

Promise.all 和 Promise.race 的区别

基本概念对比

特性 Promise.all Promise.race
执行机制 等待所有Promise完成 只要有一个Promise完成就返回
返回值 所有Promise结果的数组 第一个完成的Promise结果
错误处理 有一个失败立即拒绝 第一个完成的(成功或失败)
适用场景 并行处理,需要所有结果 竞速,超时控制,最快响应

详细解析

1. Promise.all

特点: 等待所有Promise完成,全部成功才算成功,有一个失败立即失败

javascript 复制代码
// 基本用法
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // [1, 2, 3]
  })
  .catch(error => {
    console.error('有一个Promise失败了:', error);
  });

成功案例:

javascript 复制代码
const urls = ['/api/user', '/api/posts', '/api/settings'];

// 并行请求多个接口
Promise.all(urls.map(url => fetch(url)))
  .then(responses => {
    // 所有请求都成功完成
    return Promise.all(responses.map(response => response.json()));
  })
  .then(data => {
    console.log('用户数据:', data[0]);
    console.log('文章数据:', data[1]);
    console.log('设置数据:', data[2]);
  })
  .catch(error => {
    console.error('某个请求失败了:', error);
  });

失败案例:

javascript 复制代码
const promise1 = Promise.resolve('成功1');
const promise2 = Promise.reject('失败啦!');
const promise3 = Promise.resolve('成功3');

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // 不会执行到这里
  })
  .catch(error => {
    console.error('捕获到错误:', error); // "失败啦!"
  });

2. Promise.race

特点: 竞速模式,哪个Promise先完成就返回哪个的结果

javascript 复制代码
// 基本用法
const promise1 = new Promise((resolve) => {
  setTimeout(() => resolve('第一个完成'), 1000);
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => resolve('第二个完成'), 500);
});

Promise.race([promise1, promise2])
  .then(result => {
    console.log(result); // "第二个完成" (因为500ms比1000ms快)
  })
  .catch(error => {
    console.error('第一个完成的Promise失败了:', error);
  });

实际应用场景

Promise.all 应用场景

1. 并行处理多个独立任务
javascript 复制代码
// 同时获取用户信息和订单信息
function getUserDashboard(userId) {
  const userPromise = fetch(`/api/users/${userId}`);
  const ordersPromise = fetch(`/api/users/${userId}/orders`);
  const settingsPromise = fetch(`/api/users/${userId}/settings`);

  return Promise.all([userPromise, ordersPromise, settingsPromise])
    .then(responses => Promise.all(responses.map(r => r.json())))
    .then(([user, orders, settings]) => {
      return {
        user,
        orders,
        settings
      };
    });
}
2. 批量数据处理
javascript 复制代码
// 批量上传文件
function uploadFiles(files) {
  const uploadPromises = files.map(file => {
    return new Promise((resolve, reject) => {
      const formData = new FormData();
      formData.append('file', file);
      
      fetch('/api/upload', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json())
      .then(resolve)
      .catch(reject);
    });
  });

  return Promise.all(uploadPromises)
    .then(results => {
      console.log('所有文件上传完成:', results);
      return results;
    });
}

Promise.race 应用场景

1. 请求超时控制
javascript 复制代码
// 设置请求超时
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('请求超时')), timeout);
  });

  return Promise.race([fetchPromise, timeoutPromise]);
}

// 使用示例
fetchWithTimeout('/api/data', 3000)
  .then(response => response.json())
  .then(data => console.log('数据:', data))
  .catch(error => {
    if (error.message === '请求超时') {
      console.log('请求超时,显示备用数据');
    } else {
      console.error('其他错误:', error);
    }
  });
2. 竞速获取资源
javascript 复制代码
// 从多个CDN源获取资源,使用最快的响应
function getFastestResource(resourceUrls) {
  const fetchPromises = resourceUrls.map(url => 
    fetch(url).then(response => response.json())
  );

  return Promise.race(fetchPromises)
    .then(data => {
      console.log('使用最快响应的数据源');
      return data;
    });
}

// 使用多个CDN源
const cdnUrls = [
  'https://cdn1.example.com/data.json',
  'https://cdn2.example.com/data.json',
  'https://cdn3.example.com/data.json'
];

getFastestResource(cdnUrls).then(data => {
  // 使用最快返回的数据
});

高级用法和区别演示

综合对比示例

javascript 复制代码
// 创建测试Promise
const createPromise = (value, delay, shouldReject = false) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldReject) {
        reject(`错误: ${value}`);
      } else {
        resolve(`成功: ${value}`);
      }
    }, delay);
  });
};

const p1 = createPromise('A', 1000);
const p2 = createPromise('B', 500);
const p3 = createPromise('C', 2000);

console.log('=== Promise.all 测试 ===');
Promise.all([p1, p2, p3])
  .then(results => {
    console.log('Promise.all 结果:', results);
    // ["成功: A", "成功: B", "成功: C"] (等待所有完成)
  })
  .catch(error => {
    console.log('Promise.all 错误:', error);
  });

console.log('=== Promise.race 测试 ===');
Promise.race([p1, p2, p3])
  .then(result => {
    console.log('Promise.race 结果:', result);
    // "成功: B" (最快完成的)
  })
  .catch(error => {
    console.log('Promise.race 错误:', error);
  });

错误处理对比

javascript 复制代码
// 包含错误的Promise测试
const successPromise = createPromise('成功', 1000);
const errorPromise = createPromise('错误', 500, true);
const latePromise = createPromise('迟到的', 2000);

console.log('=== Promise.all 错误处理 ===');
Promise.all([successPromise, errorPromise, latePromise])
  .then(results => {
    console.log('不会执行这里');
  })
  .catch(error => {
    console.log('Promise.all 捕获错误:', error); // "错误: 错误"
    // 立即失败,不会等待其他Promise
  });

console.log('=== Promise.race 错误处理 ===');
Promise.race([successPromise, errorPromise, latePromise])
  .then(result => {
    console.log('Promise.race 成功:', result);
  })
  .catch(error => {
    console.log('Promise.race 捕获错误:', error); // "错误: 错误"
    // 第一个完成的(无论成功失败)
  });

手写实现

手写 Promise.all

javascript 复制代码
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    const results = [];
    let completedCount = 0;
    
    if (promises.length === 0) {
      return resolve(results);
    }
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          results[index] = value;
          completedCount++;
          
          if (completedCount === promises.length) {
            resolve(results);
          }
        })
        .catch(reject); // 任何一个失败立即拒绝
    });
  });
};

手写 Promise.race

javascript 复制代码
Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('参数必须是数组'));
    }
    
    if (promises.length === 0) {
      return; // 永远pending
    }
    
    promises.forEach(promise => {
      Promise.resolve(promise)
        .then(resolve)  // 第一个成功的
        .catch(reject); // 第一个失败的
    });
  });
};

总结表格

方面 Promise.all Promise.race
执行策略 全部完成 竞速完成
返回值 结果数组 单个结果
成功条件 全部成功 第一个成功
失败条件 任一失败 第一个失败
等待时间 最慢的完成时间 最快的完成时间
内存占用 保存所有结果 只保存一个结果
典型用途 并行计算、批量操作 超时控制、竞速请求

使用建议

  1. 使用 Promise.all 当:

    • 需要所有异步操作的结果
    • 操作之间相互独立
    • 可以容忍最慢的操作时间
  2. 使用 Promise.race 当:

    • 只需要最快的结果
    • 实现超时机制
    • 多个备用方案竞争
  3. 注意事项:

    • Promise.all 中一个失败会导致整个失败
    • Promise.race 不保证结果的稳定性
    • 都要做好错误处理

Promise.all 返回顺序与执行顺序的关系

结论

Promise.all 返回结果的顺序与传入的Promise数组顺序完全一致,与执行完成的先后顺序无关。

详细解析

1. 基本行为演示

javascript 复制代码
// 创建不同耗时的Promise
const createPromise = (value, delay) => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`完成: ${value}, 耗时: ${delay}ms`);
      resolve(value);
    }, delay);
  });
};

const promise1 = createPromise('第一个', 1000);  // 最慢
const promise2 = createPromise('第二个', 300);   // 最快
const promise3 = createPromise('第三个', 500);   // 中等

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log('最终结果顺序:', results);
    // 输出: ["第一个", "第二个", "第三个"]
    // 尽管执行完成顺序是: 第二个 -> 第三个 -> 第一个
  });

// 控制台输出:
// 完成: 第二个, 耗时: 300ms
// 完成: 第三个, 耗时: 500ms  
// 完成: 第一个, 耗时: 1000ms
// 最终结果顺序: ["第一个", "第二个", "第三个"]

2. 实现原理

Promise.all 内部会维护结果的顺序:

javascript 复制代码
// 手写实现,展示顺序保持原理
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    const results = new Array(promises.length);
    let completedCount = 0;
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          // 关键:根据原始索引存储结果
          results[index] = value;
          completedCount++;
          
          if (completedCount === promises.length) {
            resolve(results); // 按原始顺序返回
          }
        })
        .catch(reject);
    });
  });
};

3. 实际应用场景

场景1:批量请求保持顺序
javascript 复制代码
// 批量获取用户信息,需要保持返回顺序
const userIds = [1, 2, 3, 4, 5];

function getUserInfo(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(user => {
      console.log(`获取到用户 ${userId} 的数据`);
      return user;
    });
}

// 尽管网络请求完成时间不同,但结果顺序与 userIds 一致
Promise.all(userIds.map(getUserInfo))
  .then(users => {
    // users[0] 对应 userIds[0] 的用户
    // users[1] 对应 userIds[1] 的用户
    // 以此类推...
    console.log('用户顺序保持不变:', users.map(user => user.id));
    // 输出: [1, 2, 3, 4, 5]
  });
场景2:文件处理保持顺序
javascript 复制代码
// 批量处理文件,需要保持处理前后的顺序对应
const files = ['file1.txt', 'file2.txt', 'file3.txt'];

function processFile(filename, index) {
  return new Promise(resolve => {
    const processTime = Math.random() * 1000 + 500; // 随机处理时间
    setTimeout(() => {
      console.log(`处理完成: ${filename}, 索引: ${index}`);
      resolve({
        originalName: filename,
        processedName: `processed_${filename}`,
        index: index
      });
    }, processTime);
  });
}

Promise.all(files.map((file, index) => processFile(file, index)))
  .then(processedFiles => {
    console.log('最终结果顺序:');
    processedFiles.forEach((file, index) => {
      console.log(`结果[${index}]: ${file.originalName} -> ${file.processedName}`);
    });
    // 尽管处理完成顺序随机,但最终数组顺序与 files 数组一致
  });

4. 与执行完成顺序的对比

javascript 复制代码
// 明确展示执行顺序 vs 返回顺序
const tasks = [
  { name: '任务A', time: 2000 },
  { name: '任务B', time: 500 },
  { name: '任务C', time: 1000 },
  { name: '任务D', time: 300 }
];

const promises = tasks.map((task, index) => {
  return new Promise(resolve => {
    setTimeout(() => {
      const completionInfo = {
        taskName: task.name,
        originalIndex: index,
        completedAt: new Date().toISOString()
      };
      console.log(`✅ 完成: ${task.name} (原索引: ${index})`);
      resolve(completionInfo);
    }, task.time);
  });
});

console.log('开始执行Promise.all...');
console.log('预期返回顺序:', tasks.map(t => t.name));

Promise.all(promises)
  .then(results => {
    console.log('\n📋 最终返回顺序:');
    results.forEach((result, index) => {
      console.log(`结果[${index}]: ${result.taskName} (原索引: ${result.originalIndex})`);
    });
    
    console.log('\n⏱️ 实际完成顺序:');
    const sortedByCompletion = [...results].sort((a, b) => 
      new Date(a.completedAt) - new Date(b.completedAt)
    );
    sortedByCompletion.forEach((result, index) => {
      console.log(`第${index + 1}个完成: ${result.taskName}`);
    });
  });

// 输出示例:
// 开始执行Promise.all...
// 预期返回顺序: ["任务A", "任务B", "任务C", "任务D"]
// ✅ 完成: 任务D (原索引: 3)
// ✅ 完成: 任务B (原索引: 1)  
// ✅ 完成: 任务C (原索引: 2)
// ✅ 完成: 任务A (原索引: 0)
//
// 📋 最终返回顺序:
// 结果[0]: 任务A (原索引: 0)
// 结果[1]: 任务B (原索引: 1)
// 结果[2]: 任务C (原索引: 2) 
// 结果[3]: 任务D (原索引: 3)
//
// ⏱️ 实际完成顺序:
// 第1个完成: 任务D
// 第2个完成: 任务B
// 第3个完成: 任务C
// 第4个完成: 任务A

5. 错误情况下的顺序保证

javascript 复制代码
// 即使有Promise失败,已完成的结果顺序仍然保持
const mixedPromises = [
  Promise.resolve('成功1'),
  Promise.reject('错误!'),
  Promise.resolve('成功3'),
  Promise.resolve('成功4')
];

// 在第二个Promise失败前,第一个已经成功,但结果不会包含在错误中
Promise.all(mixedPromises)
  .then(results => {
    console.log('成功结果:', results);
  })
  .catch(error => {
    console.log('捕获错误:', error); // "错误!"
    // 注意:即使第一个Promise已经成功完成,我们也不会看到它的结果
    // 整个Promise.all立即拒绝,不等待其他Promise
  });

6. 与其他方法的对比

与 Promise.race 对比
javascript 复制代码
const promises = [
  new Promise(resolve => setTimeout(() => resolve('慢的'), 1000)),
  new Promise(resolve => setTimeout(() => resolve('快的'), 100))
];

// Promise.all - 保持顺序
Promise.all(promises).then(results => {
  console.log('Promise.all 结果:', results); // ["慢的", "快的"]
});

// Promise.race - 只取最快
Promise.race(promises).then(result => {
  console.log('Promise.race 结果:', result); // "快的"
});
与 Promise.allSettled 对比
javascript 复制代码
// Promise.allSettled 同样保持顺序
const promisesWithErrors = [
  Promise.resolve('成功1'),
  Promise.reject('错误2'),
  Promise.resolve('成功3')
];

Promise.allSettled(promisesWithErrors)
  .then(results => {
    console.log('allSettled 结果顺序:');
    results.forEach((result, index) => {
      console.log(`[${index}]:`, result.status, result.value || result.reason);
    });
    // 顺序保持: 成功1, 错误2, 成功3
  });

重要总结

  1. 顺序保证: Promise.all 严格按传入数组顺序返回结果
  2. 索引对应: 结果数组的索引与原始Promise数组索引一一对应
  3. 与执行时间无关: 无论哪个Promise先完成,返回顺序不变
  4. 错误处理: 一旦有Promise失败,立即拒绝,不返回任何成功结果
  5. 适用场景: 需要保持处理前后顺序对应的批量异步操作

这种顺序保证的特性使得 Promise.all 在处理需要保持对应关系的批量操作时非常有用,比如:

  • 批量文件上传后需要与原文件列表对应
  • 多个API请求后需要与请求参数对应
  • 并行计算后需要与输入数据顺序对应
相关推荐
马达加斯加D5 小时前
Web身份认证 --- OAuth授权机制
前端
2401_837088505 小时前
Error:Failed to load resource: the server responded with a status of 401 ()
开发语言·前端·javascript
全栈师5 小时前
LigerUI下frm与grid的交互
java·前端·数据库
叫我詹躲躲5 小时前
被前端存储坑到崩溃?IndexedDB 高效用法帮你少走 90% 弯路
前端·indexeddb
无尽夏_5 小时前
CSS3(前端基础)
前端·css·1024程序员节
温宇飞5 小时前
Next.js 简述 - React 全栈框架
前端
百花~5 小时前
前端三剑客之一 CSS~
前端·css
青天诀5 小时前
React 中 setTimeout 获取不到最新 State 的原因及解决方案
前端·react.js
拉不动的猪5 小时前
闭包实际项目中应用场景有哪些举例
前端·javascript·面试