Promise基础用法

  • 参考文章 https://es6.ruanyifeng.com/#docs/promise#Promise-resolve
  • 原文链接 https://juejin.cn/post/7069805387490263047

    1. const promise = new Promise(function(resolve, reject) {
    2. // ... some code
    3. if (/* 异步操作成功 */){
    4. resolve(value);
    5. } else {
    6. reject(error);
    7. }
    8. });

    Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
    resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

    1. promise.then( (value) => {
    2. // success
    3. }, (error) => {
    4. // failure
    5. });

    代码示例

    下面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

    1. function loadImageAsync(url) {
    2. return new Promise((resolve, reject) => {
    3. const image = new Image();
    4. image.onload = () => {
    5. resolve(image);
    6. };
    7. image.onerror = () => {
    8. reject(new Error('Could not load image at ' + url));
    9. };
    10. image.src = url;
    11. });
    12. }

    Promise.all()

  • Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

  • Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。

    只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且reject的是第一个抛出的错误信息。

    1. Promise.all 的返回值是一个新的 Promise 实例。
    2. Promise.all 接受一个可遍历的数据容器,容器中每个元素都应是 Promise 实例。咱就是说,假设这个容器就是数组。
    3. 数组中每个 Promise 实例都成功时(由pendding状态转化为fulfilled状态),Promise.all 才成功。这些 Promise 实例所有的 resolve 结果会按照原来的顺序集合在一个数组中作为 Promise.all 的 resolve 的结果。
    4. 数组中只要有一个 Promise 实例失败(由pendding状态转化为rejected状态),Promise.all 就失败。Promise.all 的 .catch() 会捕获到这个 reject。

代码示例

  1. export function createCarMassMarks(par) {
  2. let pro1 = new Promise((resolve)=>{
  3. getOnlineVehicles().then(res => {
  4. resolve(res.data)
  5. })
  6. });
  7. //创建实例pro2
  8. let pro2 = new Promise((resolve) => {
  9. getOnlineParticipants().then(res => {
  10. resolve(res.data)
  11. })
  12. });
  13. let data = Promise.all([pro1,pro2]).then((result) => {
  14. return [...result[0],...result[1]]
  15. });
  16. return data
  17. }

下面代码中,getOnlineVehicles和getOnlineParticipants是两个异步操作,只有等到它们的结果都返回了,才会执行data这个方法。

  • 调用 createCarMassMarks ```json import {createMarks} from “../../commonJs/index”;

createMarks().then(res=>{ //数据res
})

  1. <a name="vbEhA"></a>
  2. #### 原生 Promise.all 测试
  3. ```json
  4. const p1 = Promise.resolve('p1')
  5. const p2 = new Promise((resolve, reject) => {
  6. setTimeout(() => {
  7. resolve('p2 延时一秒')
  8. }, 1000)
  9. })
  10. const p3 = new Promise((resolve, reject) => {
  11. setTimeout(() => {
  12. resolve('p3 延时两秒')
  13. }, 2000)
  14. })
  15. const p4 = Promise.reject('p4 rejected')
  16. const p5 = new Promise((resolve, reject) => {
  17. setTimeout(() => {
  18. reject('p5 rejected 延时1.5秒')
  19. }, 1500)
  20. })
  1. // 所有Promise实例都成功
  2. Promise.all([p1, p2, p3])
  3. .then(res => {
  4. console.log(res)
  5. })
  6. .catch(err => console.log(err)) // 2秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒'
  7. // 一个Promise实例失败
  8. Promise.all([p1, p2, p4])
  9. .then(res => {
  10. console.log(res)
  11. })
  12. .catch(err => console.log(err)) // p4 rejected
  13. // 一个延时失败的Promise
  14. Promise.all([p1, p2, p5])
  15. .then(res => {
  16. console.log(res)
  17. })
  18. .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected
  19. // 两个Promise实例失败
  20. Promise.all([p1, p4, p5])
  21. .then(res => {
  22. console.log(res)
  23. })
  24. .catch(err => console.log(err)) // p4 rejected

上面 p4 和 p5 在未传入 Promise.all 时需要注释掉,因为一个调用了 reject 的 Promise 实例如果没有使用 .catch() 方法去捕获错误会报错。但如果 Promise 实例定义了自己的 .catch,就不会触发 Promise.all 的 .catch() 方法。

手动实现Promise.all

  • 1、Promise.all 接受一个数组,返回值是一个新的 Promise 实例

    1. Promise.MyAll = function (promises) {
    2. return new Promise((resolve, reject) => {
    3. })
    4. }
  • 2、数组中所有 Promise 实例都成功,Promise.all 才成功。不难想到,咱得需要一个数组来收集这些 Promise 实例的 resolve 结果。但有句俗话说得好:“不怕一万,就怕万一”,万一数组里面有元素不是 Promise咋办 —— 那就得用 Promise.resolve() 把它办了。这里还有一个问题,Promise 实例是不能直接调用 resolve 方法的,咱得在 .then() 中去收集结果。注意要保持结果的顺序

    1. Promise.MyAll = function (promises) {
    2. let arr = []
    3. return new Promise((resolve, reject) => {
    4. promises.forEach((item, i) => {
    5. Promise.resolve(item).then(res => {
    6. arr[i] = res
    7. })
    8. })
    9. })
    10. }
  • 3 收集到的结果(数组arr)作为参数传给外层的 resolve 方法。这里咱们肯定是有一个判断条件的,如何判断所有 Promise 实例都成功了呢?新手容易写出这句代码

    1. if (arr.length === promises.length) resolve(arr)

    咱仔细想想 Promise 使用来干嘛的 —— 处理异步任务。对呀,异步任务很多都需要花时间呀,如果这些 Promise 中最后一个先完成呢?那 arr 数组不就只有最后一项了,前面的所有项都是 empty。所以这里咱们应该创建一个计数器,每有一个 Promise 实例成功,计数器加一:

    1. Promise.MyAll = function (promises) {
    2. let arr = [],
    3. count = 0
    4. return new Promise((resolve, reject) => {
    5. promises.forEach((item, i) => {
    6. Promise.resolve(item).then(res => {
    7. arr[i] = res
    8. count += 1
    9. if (count === promises.length) resolve(arr)
    10. })
    11. })
    12. })
    13. }
  • 4、最后就是处理失败的情况了,这里有两种写法,第一种是用 .catch() 方法捕获失败:

    1. Promise.MyAll = function (promises) {
    2. let arr = [],
    3. count = 0
    4. return new Promise((resolve, reject) => {
    5. promises.forEach((item, i) => {
    6. Promise.resolve(item).then(res => {
    7. arr[i] = res
    8. count += 1
    9. if (count === promises.length) resolve(arr)
    10. }).catch(reject)
    11. })
    12. })
    13. }
  • 第二种写法就是给 .then() 方法传入第二个参数,这个函数是处理错误的回调函数:

    1. Promise.MyAll = function (promises) {
    2. let arr = [],
    3. count = 0
    4. return new Promise((resolve, reject) => {
    5. promises.forEach((item, i) => {
    6. Promise.resolve(item).then(res => {
    7. arr[i] = res
    8. count += 1
    9. if (count === promises.length) resolve(arr)
    10. }, reject)
    11. })
    12. })
    13. }

    测试案例

  • 致此 Promise.all 大功告成,赶紧拿来测试一下: ```json const p1 = Promise.resolve(‘p1’) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(‘p2 延时一秒’) }, 1000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(‘p3 延时两秒’) }, 2000) })

const p4 = Promise.reject(‘p4 rejected’)

const p5 = new Promise((resolve, reject) => { setTimeout(() => { reject(‘p5 rejected 延时1.5秒’) }, 1500) })

// 所有 Promsie 都成功 Promise.MyAll([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // 2秒后打印 [ ‘p1’, ‘p2 延时一秒’, ‘p3 延时两秒’ ]

// 一个 Promise 失败 Promise.MyAll([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected

// 一个延时失败的 Promise Promise.MyAll([p1, p2, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // 1.5秒后打印 p5 rejected 延时1.5秒

// 两个失败的 Promise Promise.MyAll([p1, p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // p4 rejected

  1. - 与原生的 Promise.all运行结果不能说很像,只能说一模一样
  2. <a name="Ezv6a"></a>
  3. ### Promise.race
  4. > Promise.race 从字面意思理解就是赛跑,以状态变化最快的那个 Promise 实例为准,最快的 Promise 成功 Promise.race 就成功,最快的 Promise 失败 Promise.race 就失败。
  5. <a name="MONM2"></a>
  6. #### 原生 Promise.race 测试
  7. ```json
  8. const p1 = Promise.resolve('p1')
  9. const p2 = new Promise((resolve, reject) => {
  10. setTimeout(() => {
  11. resolve('p2 延时一秒')
  12. }, 1000)
  13. })
  14. const p3 = new Promise((resolve, reject) => {
  15. setTimeout(() => {
  16. resolve('p3 延时两秒')
  17. }, 2000)
  18. })
  19. const p4 = Promise.reject('p4 rejected')
  20. const p5 = new Promise((resolve, reject) => {
  21. setTimeout(() => {
  22. reject('p5 rejected 延时1秒')
  23. }, 1500)
  24. })
  1. // p1无延时,p2延时1sp3延时2s
  2. Promise.race([p1, p2, p3])
  3. .then(res => console.log(res))
  4. .catch(err => console.log(err)) // p1
  5. // p4无延时reject
  6. Promise.race([p4, p2, p3])
  7. .then(res => console.log(res))
  8. .catch(err => console.log(err)) // p4 rejected
  9. // p5 延时1.5rejectp2延时1s
  10. Promise.race([p5, p2, p3])
  11. .then(res => console.log(res))
  12. .catch(err => console.log(err)) // 1s后打印: p2 延时一秒

手写Promise.race

整体流程与 Promise 差不多,只是对数组中的 Promise 实例处理的逻辑不一样,这里我们需要将最快改变状态的 Promise 结果作为 Promise.race 的结果,相对来说就比较简单了,代码如下:

  1. Promise.MyRace = function (promises) {
  2. return new Promise((resolve, reject) => {
  3. // 这里不需要使用索引,只要能循环出每一项就行
  4. for (const item of promises) {
  5. Promise.resolve(item).then(resolve, reject)
  6. }
  7. })
  8. }

测试案例

  1. // p1无延时,p2延时1sp3延时2s
  2. Promise.MyRace([p1, p2, p3])
  3. .then(res => console.log(res))
  4. .catch(err => console.log(err)) // p1
  5. // p4无延时reject
  6. Promise.MyRace([p4, p2, p3])
  7. .then(res => console.log(res))
  8. .catch(err => console.log(err)) // p4 rejected
  9. // p5 延时1.5rejectp2延时1s
  10. Promise.MyRace([p5, p2, p3])
  11. .then(res => console.log(res))
  12. .catch(err => console.log(err)) // 1s后打印: p2 延时一秒

Promise.any

Promise.any 与 Promise.all 可以看做是相反的。Promise.any 中只要有一个 Promise 实例成功就成功,只有当所有的 Promise 实例失败时 Promise.any 才失败,此时Promise.any 会把所有的失败/错误集合在一起,返回一个失败的 promise 和AggregateError类型的实例。MDN 上说这个方法还处于试验阶段,如果 node 或者浏览器版本过低可能无法使用,各位看官自行测试下。

原生 Promise.any 测试

  1. const p1 = Promise.resolve('p1')
  2. const p2 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve('p2 延时一秒')
  5. }, 1000)
  6. })
  7. const p3 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve('p3 延时两秒')
  10. }, 2000)
  11. })
  12. const p4 = Promise.reject('p4 rejected')
  13. const p5 = new Promise((resolve, reject) => {
  14. setTimeout(() => {
  15. reject('p5 rejected 延时1.5秒')
  16. }, 1500)
  17. })
  18. // 所有 Promise 都成功
  19. Promise.any([p1, p2, p3])
  20. .then(res => console.log(res))
  21. .catch(err => console.log(err)) // p1
  22. // 两个 Promise 成功
  23. Promise.any([p1, p2, p4])
  24. .then(res => console.log(res))
  25. .catch(err => console.log(err)) // p1
  26. // 只有一个延时成功的 Promise
  27. Promise.any([p2, p4, p5])
  28. .then(res => console.log(res))
  29. .catch(err => console.log(err)) // p2 延时1
  30. // 所有 Promise 都失败
  31. Promise.any([p4, p5])
  32. .then(res => console.log(res))
  33. .catch(err => console.log(err)) // AggregateError: All promises were rejected

可以看出,如果 Promise.any 中有多个成功的 Promise 实例,则以最快成功的那个结果作为自身 resolve 的结果。

手写Promise.any

  • 依葫芦画瓢,咱们先写出 Promise.any 的整体结构

    1. Promise.MyAny = function (promises) {
    2. return new Promise((resolve, reject) => {
    3. promises.forEach((item, i) => {
    4. })
    5. })
    6. }
  • 这里跟Promise.all 的逻辑是反的,咱们需要收集 reject 的 Promise,也需要一个数组和计数器,用计数器判断是否所有的 Promise 实例都失败。另外在收集失败的 Promise 结果时咱需要打上一个失败的标记方便分析结果。

    1. Promise.MyAny = function (promises) {
    2. let arr = [],
    3. count = 0
    4. return new Promise((resolve, reject) => {
    5. promises.forEach((item, i) => {
    6. Promise.resolve(item).then(resolve, err => {
    7. arr[i] = { status: 'rejected', val: err }
    8. count += 1
    9. if (count === promises.length) reject(new Error('没有promise成功'))
    10. })
    11. })
    12. })
    13. }
  • 这里我没有使用 MDN 上规定的 AggregateError 实例,手写嘛,随心所欲一点,写自己看着舒服的😄

    测试案例

    Promise.allSettled

    有时候,咱代码人总是会有点特殊的需求:如果咱希望一组 Promise 实例无论成功与否,都等它们异步操作结束了在继续执行下一步操作,这可如何是好?于是就出现了 Promise.allSettled。

  1. const p1 = Promise.resolve('p1')
  2. const p2 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve('p2 延时一秒')
  5. }, 1000)
  6. })
  7. const p3 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve('p3 延时两秒')
  10. }, 2000)
  11. })
  12. const p4 = Promise.reject('p4 rejected')
  13. const p5 = new Promise((resolve, reject) => {
  14. setTimeout(() => {
  15. reject('p5 rejected 延时1.5秒')
  16. }, 1500)
  17. })
  18. // 所有 Promise 实例都成功
  19. Promise.allSettled([p1, p2, p3])
  20. .then(res => console.log(res))
  21. .catch(err => console.log(err))
  22. // [
  23. // { status: 'fulfilled', value: 'p1' },
  24. // { status: 'fulfilled', value: 'p2 延时一秒' },
  25. // { status: 'fulfilled', value: 'p3 延时两秒' }
  26. // ]
  27. // 有一个 Promise 失败
  28. Promise.allSettled([p1, p2, p4])
  29. .then(res => console.log(res))
  30. .catch(err => console.log(err))
  31. // [
  32. // { status: 'fulfilled', value: 'p1' },
  33. // { status: 'fulfilled', value: 'p2 延时一秒' },
  34. // { status: 'rejected' , value: 'p4 rejected' }
  35. // ]
  36. // 所有 Promise 都失败
  37. Promise.allSettled([p4, p5])
  38. .then(res => console.log(res))
  39. .catch(err => console.log(err))
  40. // [
  41. // { status: 'rejected', reason: 'p4 rejected' },
  42. // { status: 'rejected', reason: 'p5 rejected 延时1.5秒' }
  43. // ]

可以看到,与 Promise.any 类似,Promise.allSettled 也给所有收集到的结果打上了标记。而且 Promise.allSettled 是不会变成 rejected 状态的,不管一组 Promise 实例的各自结果如何,Promise.allSettled 都会转变为 fulfilled 状态。

手写 Promise.allSettled

咱就是说,得用个数组把所有的 Promise 实例的结果(无论成功与否)都收集起来,判断收集完了(所有 Promise 实例状态都改变了),咱就将这个收集到的结果 resolve 掉。收集成功 Promise 结果的逻辑咱们在 Promise.all 中实现过,收集失败 Promise 结果咱们在 Promise.any 中处理过。这波,这波是依葫芦画瓢——照样。

  1. Promise.MyAllSettled = function (promises) {
  2. let arr = [],
  3. count = 0
  4. return new Promise((resolve, reject) => {
  5. promises.forEach((item, i) => {
  6. Promise.resolve(item).then(res => {
  7. arr[i] = { status: 'fulfilled', val: res }
  8. count += 1
  9. if (count === promises.length) resolve(arr)
  10. }, (err) => {
  11. arr[i] = { status: 'rejected', val: err }
  12. count += 1
  13. if (count === promises.length) resolve(arr)
  14. })
  15. })
  16. })
  17. }

这代码,逻辑上虽说没问题,但各位优秀的程序员们肯定是看不顺眼的,怎么会有两段重复的代码捏,不行,咱得封装一下

  1. Promise.MyAllSettled = function (promises) {
  2. let arr = [],
  3. count = 0
  4. return new Promise((resolve, reject) => {
  5. const processResult = (res, index, status) => {
  6. arr[index] = { status: status, val: res }
  7. count += 1
  8. if (count === promises.length) resolve(arr)
  9. }
  10. promises.forEach((item, i) => {
  11. Promise.resolve(item).then(res => {
  12. processResult(res, i, 'fulfilled')
  13. }, err => {
  14. processResult(err, i, 'rejected')
  15. })
  16. })
  17. })
  18. }
  • perfect,俗话说得好:没病走两步。老样子,给代码跑几个案例。

    测试案例

    ```json // 所有 Promise 实例都成功 Promise.MyAllSettled([p1, p2, p3]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: ‘fulfilled’, value: ‘p1’ }, // { status: ‘fulfilled’, value: ‘p2 延时一秒’ }, // { status: ‘fulfilled’, value: ‘p3 延时两秒’ } // ]

// 有一个 MyAllSettled 失败 Promise.allSettled([p1, p2, p4]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: ‘fulfilled’, value: ‘p1’ }, // { status: ‘fulfilled’, value: ‘p2 延时一秒’ }, // { status: ‘rejected’ , value: ‘p4 rejected’ } // ]

// 所有 MyAllSettled 都失败 Promise.allSettled([p4, p5]) .then(res => console.log(res)) .catch(err => console.log(err)) // [ // { status: ‘rejected’, reason: ‘p4 rejected’ }, // { status: ‘rejected’, reason: ‘p5 rejected 延时1.5秒’ } // ] ```

  • 致此,大功告成,我可以骄傲地对妈妈说:“妈妈,我再也不怕 Promise.all”了