Promise 处理多个异步

272 阅读6分钟

引言

在现代的 JavaScript 开发中,异步编程是一个非常重要的概念。随着前端应用的复杂度不断增加,我们经常需要同时处理多个异步任务。为了更好地管理这些任务,JavaScript 提供了多种 Promise 组合方法,如 Promise.allPromise.racePromise.allSettledPromise.any。本文将深入探讨这些方法的使用方法和区别。

2222.jpg

Promise

在开始之前,我们先简单回顾一下 Promise 的基本概念。Promise 是 JavaScript 中用于处理异步操作的对象。它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:

  • Pending:初始状态,既不是成功,也不是失败。
  • Fulfilled:操作成功完成。
  • Rejected:操作失败。

并发与并行

并发(Concurrency)

指的是多个任务在同一时间段内交替执行,但不一定是同时执行。在单核 CPU 上,多个任务通过时间片轮转的方式实现并发。Promise.all是一种伪并发。

在单线程环境中,如 JavaScript 的浏览器运行时或 Node.js 环境中,这些任务并不是同时执行的,而是通过事件循环和异步I/O快速切换执行上下文来实现看起来像是同时发生的效果。使用 Promise.all 可以让多个异步操作并发执行,这意味着它们开始的时间点很接近,并且等待所有操作完成后再继续后续步骤。

并行(Parallelism)

指的是多个任务在同一时刻同时执行。在多核 CPU 上,多个任务可以真正同时执行。这通常需要多核处理器的支持,通过操作系统调度不同的线程或进程在不同的CPU核心上同时执行任务。

JavaScript 本身是单线程的,因此不能直接实现并行执行。然而,在某些特定场景下,比如使用 Web Workers 在浏览器端或者 worker threads 在 Node.js 中,可以创建额外的线程来实现真正的并行处理。

Promise 同时处理多个异步任务

当你需要同时处理多个异步任务时,Promise 给出了几种方法,它们都需要传入一个全为 Promise 的数组作为参数, 本例中我将会使用到的 Promise 数组如下

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ temp: 29,conditons: 'Sunny with Clouds'})
    // reject('error')
  },3000)
})
const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
    // reject('BBQ 糊了')
  },5000)
})

Promise.all:等待所有任务完成

Promise.all 是 JavaScript 中最常用的 Promise 组合方法之一。它接受一个 Promise 实例数组作为参数,并返回一个新的 Promise。这个新的 Promise 会在所有传入的 Promise 都成功完成时被解决,返回一个包含所有结果的数组。如果其中任何一个 Promise 失败,Promise.all 会立即返回失败的结果。Promise.all 返回的结果数组会按照传入的 Promise 顺序排列,而不是按照完成的顺序。

Promise
  .all([weather,tweets])  // 只要有一个错的 就返回错误 全对全返回
  .then(responses => {
    console.log(responses); // 按实例的顺序 与实例中的setTimeout 无关 
  })
  .catch(err => {
    console.log(err);
  })

当 Promise 全部成功时

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error')
  },3000)
})
const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
  },5000)
})

image.png 其中有 Promise 失败

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    
  },3000)
})
const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
  },5000)
})

image.png

Promise.race:返回第一个完成的任务

Promise.race 是另一个常用的 Promise 组合方法。它同样接受一个 Promise 实例数组作为参数,但不同的是,Promise.race 会返回第一个被解决(无论是成功还是失败)的 Promise 的结果。 Promise.race 通常用于需要快速响应的场景。

Promise
  .race([weather,tweets])  // 不考虑成功还是失败 只返回最快的那一个
  .then(responses => {
    console.log(responses); // 按实例的顺序 与实例中的setTimeout 无关 
  })
  .catch(err => {
    console.log(err);
  })

最快的那个成功

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ temp: 29,conditons: 'Sunny with Clouds'})
  },3000)
})

const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
    // reject('BBQ 糊了')
  },5000)
})

image.png 最快的那个失败

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error')
  },3000)
})

const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
    // reject('BBQ 糊了')
  },5000)
})

image.png

Promise.allSettled:返回所有任务的结果

Promise.allSettled 是 ES2020 引入的新方法。它接受一个 Promise 实例数组作为参数,并返回一个新的 Promise。这个新的 Promise 会在所有传入的 Promise 都完成(无论是成功还是失败)时被解决,返回一个包含所有结果的数组。Promise.allSettled 非常适合用于需要处理多个异步任务,并且希望获取所有任务的结果(无论成功还是失败)的场景。Promise.allSettled 返回的结果数组中的每个元素都是一个对象,包含 status 和 value(成功) 或 reason(失败) 属性。

代码示例

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve({ temp: 29,conditons: 'Sunny with Clouds'})
    reject('error')
  },3000)
})

const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
    // reject('BBQ 糊了')
  },5000)
})
Promise
  .allSettled([weather,tweets]) // 全部执行完毕 不管成功还是失败
  .then(responses => {
    console.log(responses); // 按实例的顺序 与实例中的setTimeout 无关 
  })
  .catch(err => {
    console.log(err);
  })

返回全部结果 image.png

Promise.any:返回第一个成功的任务

Promise.any 是 ES2021 引入的新方法。它接受一个 Promise 实例数组作为参数,并返回一个新的 Promise。这个新的 Promise 会在第一个成功的 Promise 完成时被解决,返回该 Promise 的结果。如果所有 Promise 都失败,Promise.any 会返回一个 AggregateErrorPromise.any 非常适合用于需要获取第一个成功结果的场景。如果所有 Promise 都失败,Promise.any 会返回一个 AggregateError,包含所有失败的原因。

代码示例

Promise
  .any([weather,tweets]) // 只要有一个成功就返回 只返回一个
  .then(responses => {
    console.log(responses); // 按实例的顺序 与实例中的setTimeout 无关 
  })
  .catch(err => {
    console.log(err);
  })

返回第一个成功

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error')
  },3000)
})

const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
  },5000)
})

image.png 如果都成功,返回第一个

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ temp: 29,conditons: 'Sunny with Clouds'})
  },3000)
})

const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('I like cake, BBQ is ready now!')
  },5000)
})

如果全都失败,返回一个 AggregateError,包含所有失败的原因

const weather = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('error')
  },3000)
})
const tweets = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('BBQ 糊了')
  },5000)
})

image.png

总结

  • Promise.all:只要有一个错的 就返回错误 全对全返回。
  • Promise.race:不考虑成功还是失败 只返回最快的那一个。
  • Promise.allSettled:全部执行完毕 不管成功还是失败。
  • Promise.any:只要有一个成功就返回 只返回一个。

在实际开发中,根据具体需求选择合适的 Promise 组合方法,可以大大提高代码的效率和可读性。下一篇文章将会趁热介绍 手写Promsie.all。