Promise.all
Promise.all
方法
- 接收一个 Promise 数组
- 返回一个 Promise,用
p
缓存
在传给 Promise.all 的所有 Promise 都兑现后,p
也会随之兑现并按照传入顺序返回各 Promise 兑现的结果数组
但只要有一个 Promise 被拒绝,那么 p 就会立即以该 Promise 的拒绝理由确定为被拒绝。
同时异步请求多个数据的时候,即并行,就会使用用all
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 2000);
})
// Prmose.all
console.time('cost')
Promise.all([p1, p2]).then(data => {
console.log(data);
console.timeEnd('cost')
})
输出结果是
[ 'p1', 'p2' ]
cost: 2026.419ms
代码实现
调用的使用是通过Promise.all
,说明这个方法是在类上的,而不是在实例上的,故这里使用static
class Promise{
//...
static all(proArr) {
return new Promise((resolve, reject) => {
// ...
})
}
}
我们最后返回的是proArr
里面各个promise
返回的值,而且还是按promise
的顺序存储的数组
所以我们设置一个数组ret = []
来缓存数据
我们遍历proArr
执行里面的promise
static all(proArr) {
return new Promise((resolve, reject) => {
let ret = []
let done = (i, data) => {
ret[i] = data
}
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i,data) , reject)
}
})
}
这里我们需要一个辅助函数done
来完成每个 promise
成功返回的值的存储
那为什么不直接
proArr[i].then(data => ret[i] = data, reject)
因为我们还需要一个变量来告诉我们ret
里数据是否已经装满了
装满的时候我们就执行resolve(ret)
发送出去
static all(proArr) {
return new Promise((resolve, reject) => {
let ret = []
let count = 0
let done = (i, data) => {
ret[i] = data
if(++count === proArr.length) resolve(ret)
}
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i,data) , reject)
}
})
}
为什么要使用一个计数变量count
呢?我们来写一个没有使用count
的错误写法
易错点
这也是容易出bug的原因
static all(proArr) {
return new Promise((resolve, reject) => {
let ret = []
let done = (i, data) => {
ret[i] = data
if(i === proArr.length -1) resolve(ret)
}
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i,data) , reject)
}
})
}
我们使用i
等于proArr.length -1
作为数据装满时的触发条件
看起来就像执行完proArr
最后一个promise
时我们就执行resolve(ret)
这是因为没有理解Promise.then
的意义,then
方法是往promise
绑定方法
这绑定的方法不是立即执行的,是then
所属的promise
成功或者失败后完成的
那如果我们按上面这种写法,如果proArr[0]
要5s后才完成,即5秒后才会执行
done = (0, data) => {
ret[0] = data
if(i === proArr.length -1) resolve(ret)
}
那proArr[2]
是1秒后就完成了,那1秒后就会执行
done = (2, data) => {
ret[2] = data
if(i === proArr.length -1) resolve(ret)
}
此时因为proArr.length - 1
等于2,所以就执行了resolve(ret)
然而我们知道此时的proArr[0]
还没执行,即我们还没往ret[0]
里面添加数据呢
使用count
的原因就在这里,proArr
中任意一个填充完数据后,即执行++count
当最后一个执行++count
时,++count === proArr.length
于是就可以执行resolve(ret)
柯里化
上面的需求就很符合柯里化的写法
static all(proArr) {
return new Promise((resolve, reject) => {
let done = (function (len, cb) {
const ret = []
let count = 0
return function (i, data) {
ret[i] = data
if (++count === len) cb(ret)
}
})(proArr.length, resolve)
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i, data), reject)
}
})
}
当然看起来也差不多,当然,因为这么写的话,我们可以把函数外移,那逻辑代码就很清晰了
Promise{
// ...
static all(proArr) {
return new Promise((resolve, reject) => {
let done = currying(proArr.length, resolve)
for (let i = 0; i < proArr.length; i++) {
proArr[i].then(data => done(i, data), reject)
}
})
}
}
function currying(len, cb) {
const ret = []
let count = 0
return function (i, data) {
ret[i] = data
if (++count === len) cb(ret)
}
}
Promise.race
同all一样接受一个数组,该数组里面都是promise对象。
区别在于,结果是返回最先返回的promise数据,这里的数据是最先返回的单个数据。有点像是比赛,谁先到就用谁。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 2000);
})
// Promise.race
console.time('cost')
Promise.race([p1, p2]).then(data => {
console.log(data);
console.timeEnd('cost')
})
输出结果是
p1
cost: 1014.655ms
当有调用的接口不稳定的时候,我们可以取多个,先获取到哪个就使用哪个。
还能拿来给promise做个定时器,如果在规定时间没到,我们就执行一个reject
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 2000);
})
function tiemout(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('tiemout')
}, delay);
})
}
Promise.race([p2, tiemout(500)]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
如果过了500ms时p2还没有获得数据回来,那么timout就会执行reject,报 timeout
那实现起来也很方便
static race(promiseAry) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseAry.length; i++) {
promiseAry[i].then(resolve, reject)
}
})
}
这么简单得益于promise的状态只能改变一次,即resolve和reject都只被能执行一次,我们把返回的promie里的resolve和reject方法放在了promiseAry每一个promise的成功或者失败回调里面,当其中任意一个成功或失败后就会调用我们传进去的resolve和reject,一旦调用了,就不会再次调用。
catch && resolve && reject
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
return new Promise((resolve, reject) => resolve(value))
}
static reject(reason) {
return new Promise((resolve, reject) => reject(reason))
}
上面的实现就很简单的,没什么好讲的,这里可以说一个catch
我们平时在写then时,有两个参数,一个成功一个失败,连在一起写,挺容易看混的,所以有一种实践就是then方法里只写成功处理函数,然后后面添加一个catch来处理失败
Promise.reject('失败')
.then(data => { console.log(data) })
.catch(reason => { console.log(reason) })
这里运用的就是,我们在then源码实现时,如果rejected不是一个函数就会把错误往后抛的原理。
源码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let then
let called = false
if (x instanceof Promise) {
if (x.status == PENDING) {
x.then(
y => resolvePromise(promise2, y, resolve, reject),
r => reject(r)
)
} else x.then(resolve, reject);
} else if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
try {
then = x.then;
if (typeof then == 'function') {
then.call(
x,
y => {
//防止promise会同时执行成功和失败的回调
//如果promise2已经成功或失败了,则不会再处理了
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
//防止promise会同时执行成功和失败的回调
//如果promise2已经成功或失败了,则不会再处理了
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
// function gen(times, cb) {
// let ret = []
// let count = 0
// return function (i, data) {
// ret[i] = data
// if (++count === times) {
// cb(ret)
// }
// }
// }
class Promise {
constructor(executor) {
// 设置状态
this.status = PENDING
this.value = undefined
// 定义存放 成功后 执行的回调数组
this.onResolvedCallbacks = []
// 定义存放 失败后 执行的回调数组
this.onRejectedCallbacks = []
let resolve = data => {
let timer = setTimeout(() => {
clearTimeout(timer)
if (this.status === PENDING) {
this.status = FULFILLED
this.value = data
this.onResolvedCallbacks.forEach(cb => cb(this.value))
}
})
}
let reject = reason => {
let timer = setTimeout(() => {
clearTimeout(timer)
if (this.status === PENDING) {
this.status = REJECTED
this.value = reason
this.onRejectedCallbacks.forEach(cb => cb(this.value))
}
})
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
let promise2
if (this.status === FULFILLED) {
promise2 = new Promise((resolve, reject) => {
let timer = setTimeout(() => {
clearTimeout(timer)
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.status === REJECTED) {
promise2 = new Promise((resolve, reject) => {
let timer = setTimeout(() => {
clearTimeout(timer)
try {
let x = onRejected(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.status === PENDING) {
promise2 = new Promise((resolve, reject) => {
this.onResolvedCallbacks.push(value => {
try {
let x = onFulfilled(value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onRejectedCallbacks.push(reason => {
try {
let x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
return promise2
};
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
return new Promise((resolve, reject) => resolve(value))
}
static reject(reason) {
return new Promise((resolve, reject) => reject(reason))
}
// static all(promiseAry) {
// return new Promise((resolve, reject) => {
// let done = gen(promiseAry.length, resolve)
// for (let i = 0; i < promiseAry.length; i++) {
// promiseAry[i].then(
// data => { done(i, data) },
// reject)
// }
// })
// }
static all(promiseAry) {
return new Promise((resolve, reject) => {
let ret = []
let count = 0
for (let i = 0; i < promiseAry.length; i++) {
promiseAry[i].then(
data => {
ret[i] = data
if (++count === promiseAry.length) {
resolve(ret)
}
},
reason => reject(reason)
)
}
})
}
static race(promiseAry) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseAry.length; i++) {
promiseAry[i].then(resolve, reject)
}
})
}
}
// 测试
Promise.deferred = Promise.defer = function () {
var defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
try {
module.exports = Promise
} catch (e) {
}