Promise.all 与 Promise.race:并行与竞态的异步控制

Promise.all 与 Promise.race:异步世界的两匹骏马

在 JavaScript 异步编程的广阔草原上,Promise 就像一匹骏马,带着我们驰骋于各种异步操作之间。而 Promise.allPromise.race,就像两匹性格迥异,用途不同的骏马,帮助我们更好地驾驭 Promise,更优雅地控制异步流程。

想象一下,你是一位美食博主,准备制作一道美食视频,需要同时完成以下几件事:

  • 购买食材 (fetchIngredients): 从农贸市场获取新鲜食材,这需要网络请求,耗时不定。
  • 准备拍摄场地 (setupStudio): 布置好灯光、背景,确保拍摄环境完美。
  • 撰写解说词 (writeScript): 构思精彩的解说词,让视频更加生动有趣。

这三件事互相独立,可以同时进行,不必等待彼此完成。这时候,Promise.all 这匹“齐头并进”的骏马就派上用场了。

Promise.all:一个都不能少!

Promise.all 接收一个 Promise 数组(或者任何 iterable 对象,只要它能被 Promise.resolve 处理),它会并行地执行所有 Promise,并返回一个新的 Promise。这个新的 Promise 只有在所有 Promise 都成功完成时才会 resolve,并且 resolve 的值是一个包含所有 Promise resolve 值的数组,顺序与传入的 Promise 数组保持一致。

如果其中任何一个 Promise rejected,那么 Promise.all 返回的 Promise 也会立即 rejected,并且 reject 的原因是第一个被 rejected 的 Promise 的原因。

回到美食博主的例子,我们可以这样使用 Promise.all

function fetchIngredients() {
  return new Promise(resolve => setTimeout(() => {
    resolve("新鲜食材已购入!");
  }, 2000));
}

function setupStudio() {
  return new Promise(resolve => setTimeout(() => {
    resolve("拍摄场地已准备就绪!");
  }, 1000));
}

function writeScript() {
  return new Promise(resolve => setTimeout(() => {
    resolve("解说词已完成!");
  }, 1500));
}

Promise.all([fetchIngredients(), setupStudio(), writeScript()])
  .then(results => {
    console.log("所有准备工作都完成了!");
    console.log("结果:", results); // ["新鲜食材已购入!", "拍摄场地已准备就绪!", "解说词已完成!"]
    console.log("开始拍摄吧!");
  })
  .catch(error => {
    console.error("准备工作出错:", error);
  });

在这个例子中,Promise.all 会同时执行 fetchIngredientssetupStudiowriteScript 这三个 Promise。只有当所有这三个 Promise 都成功 resolve 后,才会执行 .then 中的代码,输出 "所有准备工作都完成了!" 并打印所有 Promise 的结果。

如果 fetchIngredients 因为农贸市场突然停电而失败,那么 Promise.all 就会立即 rejected,执行 .catch 中的代码,告诉你 “准备工作出错”。

应用场景:

  • 并发请求数据: 同时请求多个 API 接口,只有所有数据都返回后才进行下一步处理。
  • 资源加载: 并行加载多个图片、脚本或样式文件,加快页面加载速度。
  • 数据验证: 同时验证多个表单字段,确保所有字段都符合要求。

Promise.all 就像一个团队,要求每个人都完成自己的任务,才能一起庆祝胜利。少了一个人,整个团队就会失败。

Promise.race:先到先得!

现在想象另一种情况:你是一位赛车爱好者,正在观看一场精彩的比赛。你只想知道谁是第一个冲过终点线的选手,而不是所有选手的成绩。这时候,Promise.race 这匹“争先恐后”的骏马就派上用场了。

Promise.race 同样接收一个 Promise 数组,但它的行为与 Promise.all 完全不同。它会返回一个新的 Promise,这个新的 Promise 会采用第一个 resolve 或 reject 的 Promise 的结果(包括值和状态)。也就是说,哪个 Promise 最先完成(无论是 resolve 还是 reject),Promise.race 返回的 Promise 就会以它的结果为结果。

回到赛车的例子,我们可以这样使用 Promise.race

function racer1() {
  return new Promise(resolve => setTimeout(() => {
    resolve("Racer 1 冲过终点线!");
  }, 1500));
}

function racer2() {
  return new Promise(resolve => setTimeout(() => {
    resolve("Racer 2 冲过终点线!");
  }, 1000));
}

function racer3() {
  return new Promise(reject => setTimeout(() => {
    reject("Racer 3 撞车了!");
  }, 2000));
}

Promise.race([racer1(), racer2(), racer3()])
  .then(result => {
    console.log("比赛结束!", result); // 比赛结束! Racer 2 冲过终点线!
  })
  .catch(error => {
    console.error("比赛出现意外!", error); // 比赛出现意外! Racer 3 撞车了!
  });

在这个例子中,Promise.race 会同时执行 racer1racer2racer3 这三个 Promise。由于 racer2 最先 resolve,所以 Promise.race 返回的 Promise 会立即 resolve,并输出 "比赛结束! Racer 2 冲过终点线!"。

如果 racer3 最先 reject,那么 Promise.race 就会立即 rejected,并输出 "比赛出现意外! Racer 3 撞车了!"。

应用场景:

  • 超时控制: 设置一个超时 Promise,如果超过指定时间没有响应,则 reject,防止程序一直等待。
  • 备用方案: 同时尝试多个 API 接口,使用最先返回结果的接口。
  • 竞争资源: 多个客户端竞争同一资源,只有第一个获取到资源的客户端才能继续操作。

Promise.race 就像一场比赛,只关心谁是第一名,其他选手的结果并不重要。它只关注速度,无论是胜利还是失败,都要争先恐后。

深入理解:一些需要注意的点

  • 空数组: 如果传递给 Promise.all 的数组为空,那么它会立即 resolve,resolve 的值是一个空数组。而传递给 Promise.race 的数组为空,它会永远 pending,因为没有任何 Promise 可以 resolve 或 reject。
  • 非 Promise 值: Promise.allPromise.race 都可以接受非 Promise 值,它们会被 Promise.resolve 包装成 Promise。这使得你可以混合使用 Promise 和普通值。
  • 错误处理: Promise.all 的错误处理非常严格,只要有一个 Promise rejected,整个 Promise.all 就会 rejected。而 Promise.race 则更加宽容,它会接受第一个完成的 Promise,无论是 resolve 还是 reject。

总结:选择合适的骏马

Promise.allPromise.race 是 Promise 的两个强大的工具,它们分别适用于不同的场景。

  • Promise.all 适用于需要所有 Promise 都成功完成的场景,例如并发请求数据、资源加载、数据验证等。它强调的是完整性可靠性
  • Promise.race 适用于只需要第一个 Promise 完成的场景,例如超时控制、备用方案、竞争资源等。它强调的是速度效率

选择合适的骏马,才能更好地驾驭 Promise,更优雅地控制异步流程,编写出更加健壮、高效的代码。

希望通过这篇文章,你能够更好地理解 Promise.allPromise.race 的区别和用法,并在实际开发中灵活运用它们,让你的异步代码更加清晰、可维护。记住,代码就像艺术品,选择合适的工具,才能创造出更加精美的作品。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注