如何手写一个promise.all()

众所周知,js是一门单线程的语言。所谓单线程就是从上往下执行,也就是同步执行,跟代码顺序有关。业务中涉及到的异步开始是利用回调函数跟事件来解决的,但也因此出现了一个叫回调地狱的问题(也就是回调函数再回调,反复回调好多次….)。于是es6提供了一个叫promise的对象来解决这个问题。

这里不对promise展开赘述,不太清楚的可以移步:
es6.ruanyifeng.com/?search=%3F…

1.Promise.all的用法

Promise.all():接收一个为数组的参数,将多个Promise实例包装成一个新的Promise实例。

const myPromiseAll = Promise.all([p1,p2,p3]);
复制代码

新创建的实例(myPromiseAll)的状态有传入的p1,p2,p3决定,简单来说就是以最慢的一个为准。
resolved状态:p1,p2,p3都运行完且状态为resolved。 &&
reject状态:p1,p2,p3只要其中有一个是reject状态,就会返回reject状态,不管其他的是否运行完成。 ||

2.手动实现一个Promise.all

第一步:传入数组,包装成新的promise实例

function myPromiseAll(arr) {
  return new Promise((resolve, reject) => {
    resolve('我实现了一个Promise.all');
    // or
    // reject("没能实现~~~")
  });
}
// 测试一下
let resultTest1 = mypromise([]); // 先不考虑数组有没有元素
resultTest1.then(
  (res) => {
    console.log(res); // 打印:我实现了一个Promise.all
  },
  (err) => {
    console.log(err);
  }
);
复制代码

第二步,先做简单的完成状态:

p1、p2、p3的返回值组成一个数组,传递给实例(resultTest1)的回调函数;

function mypromise(arr) {
  let resultArr = []
  return new Promise((resolve, reject) => {

     resolve(resultArr);  // 传递一个数组出去
   
  });
}
复制代码

接下来是看参数arr,分为两种:为空 跟不为空
为空数组时,直接resolve;

function mypromise(arr) {
  let resultArr = []
  return new Promise((resolve, reject) => {
       // 数组为空,直接resolve了
        if(arr.length == 0) {
            resolve(arr);
        }
        resolve(resultArr);  // 传递一个数组出去
   } 
  });
}
复制代码

不为空数组时,又分为两种:传入的是promise实例 跟 传入的不是promise实例

function mypromise(arr) {
  let resultArr = []
  return new Promise((resolve, reject) => {
    // 数组为空,直接resolve了
    if(arr.length == 0) {
       resolve(arr);
     }
    // 对传入的数组进行遍历
    for(let i = 0; i < arr.length; i++) {
      // 如果是一个promise实例 
      if(true) {
      // todo
      resolve(resultArr);   
      } else {
          resultArr.push(arr[i]);
          //todo
          resolve(resultArr);  // 返回一个数组
      }
  }
     
  });
}
复制代码

这里利用是否有then方法来判断是否是promise实例,当所有实例都完成的时候 返回resolve状态

function mypromise(arr) {
  let resultArr = [];
  return new Promise((resolve, reject) => {
    // 数组为空,直接resolve了
    if (arr.length == 0) {
      resolve(arr);
    }
    // 对传入的数组进行遍历
    for (let i = 0; i < arr.length; i++) {
      // 如果是一个promise实例
      if (arr[i].then) {
        arr[i].then(value=>{
          console.log(value);
          resultArr[i]=value; // 将promise对象写进结果数组里,并保证一一对应
        });
        // 当所有实例都完成时,进入resolve;
        if(resultArr.length === arr.length) {
          resolve(resultArr);
        }
      } else {
        resultArr.push(arr[i]);
         // 当所有实例都完成时,进入resolve;
        if(resultArr.length === arr.length) {
          resolve(resultArr);
        }
      }
    }
  });
}
复制代码

完成情况就写完了,测试一下:
在这里插入图片描述
在这里插入图片描述
resolve情况就写完啦,接下来是reject情况:

如果传入的 promise 中有一个是reject,Promise.all 就会将失败结果返给失败状态的回调函数,而不管其它 promise 是否完成(而不是终止执行哦~~)。

建议使用catch方法捕获异常,能捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)

如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应

function mypromise(arr) {
  let resultArr = [];
  return new Promise((resolve, reject) => {
    // 数组为空,直接resolve了
    if (arr.length == 0) {
      resolve(arr);
    }
    // 对传入的数组进行遍历
    for (let i = 0; i < arr.length; i++) {
      // 如果是一个promise实例
      if (arr[i].then) {
        arr[i].then(value => { 
           resultArr[i] = value; // 将promise对象写进结果数组里
        }).catch((err) => console.log("其中至少有一个失败啦"));
        // 当所有实例都完成时,进入resolve;
        if(resultArr.length === arr.length) {
          resolve(resultArr);
        }
      } else {
      // 不是promise 则转换为promise对象。
       Promise.resolve(arr[i]).then(o => {
          resultArr[i] = o;
          if(resultArr.length === arr.length) {
            resolve(resultArr);
          }
        })
      }
    }
  });
}
复制代码

测试一波:
在这里插入图片描述

另外,reject函数不一定要写在里面,写在返回实例的catch函数里面也是可以的。
在这里插入图片描述
优化一下判断传入的是否为promise的情况,不管是不是promise,都给他resolve转换一下,最终代码为:

function myPromiseAll(arr) {
  let resultArr = [];
  return new Promise((resolve, reject) => {
    if (arr.length === 0) {
      resolve(arr);
    }
    for (let i = 0; i < arr.length; i++) {
      Promise.resolve(arr[i]).then(o => { 
        resultArr[i] = o;
        if(resultArr.length === arr.length) {
          resolve(resultArr)
        }
      }).catch(e => reject(e))
    }
  });
}
复制代码

测试一下

测试代码为:
let test1 = new Promise((res) => {
  setTimeout(res, 1000, 'test1 resolve')
});
let testPromiseArr = myPromiseAll([1, test1, 2]).then(res => console.log(res));

复制代码

执行结果截图为:
在这里插入图片描述
中间传入promise时为empty…
原因:当传入test1进去的时候,存在setTimeout的延时执行,当还没执行完成的时候,resultArr.length就已经是3了,所以还没等test1异步结束完就执行resolve(resultArr)。
解决办法:加入一个手动计数器,当该计数器等于arr.length的时候 即结束。
测试代码还是以上,测试成功。

在这里插入图片描述
最终代码为:

function myPromiseAll(arr) {
  let resultArr = [];
  return new Promise((resolve) => {
    if(arr.length === 0) {
      resolve(arr);
    }
    let count = 0;
    for(let i = 0;i < arr.length; i += 1) {
      Promise.resolve(arr[i]).then(value => {
        resultArr[i] = value;
        if(++count === arr.length) {
          resolve(resultArr);
        }
      }).catch((err) => console.log("promiseAll 出错啦",err));
   }
 })
}

测试代码:
let test1 = new Promise((res) => {
  setTimeout(res, 1000, 'test1 resolve')
})
let test2 = new Promise((res) => {
  setTimeout(res, 2000, 'test2 resolve')
})
let test3 = new Promise((res) => {
  setTimeout(res, 2000, 'test3 resolve')
})

let testAll = myPromiseAll([test1, test2, test3]).then( value => {
  console.log(value);
})
let testArr = myPromiseAll([1, 2, 3]).then(val => {
  console.log(val)
})
let testPromiseArr = myPromiseAll([1, test1, 2]).then(res => console.log(res));

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享