众所周知,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));
复制代码