大纲
前置知识
手写promise
- resolve
- reject
- then
不传参的处理
-
保证执行顺序,异步实现
then方法的代码在同步代码之后执行或者状态改变之后执行
,Promise参数函数中有异步操作时
- then的链式调用
- all
- catch
- race
前置知识
1. promise基础知识
- promise是一个对象,可以通过new Promise()的方式生成。
- promise是一个容器,存放着未来才会结束的事件,通常是一个异步操作的结果
2. promise的特点
- 对象的状态不受外界影响 ( pending, fulfilled, rejected )
- 只有异步操作的结果可以决定当前是那一种状态,其他操作都不能改变这个状态
- 状态一旦改变,就不会再变,任何时候都能得到这个结果
- promise状态改变只有两种可能,从pending变为fulfilled,从pending变为rejected
3. promise的缺点
- 无法取消promise,一旦新建就会立即执行,无法中途取消
- 如果不设置回调函数,promise内部抛出的错误不会反应到外部
- 处于pending状态时,无法得知目前进展到那一个阶段(刚开始还是即将完成)
4. resolve()
- 将promise对象的状态由:pending变为fulfilled
- 在异步操作(成功)时调用,并将异步操作的(结果)作为(参数)传递出去
- 参数可以是(正常的值)或者是一个(promise实例)
- 当resolve()函数的参数是一个promise实例对象时:
const p1 = new Promise( (resolve, reject) => {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result)).catch(error => console.log(error))
解析:
1. p2中resolve的是一个promise实例对象p1,导致p2的状态由p1决定
2. 1s时 p2状态在改变,但是此时状态改变无效,应为resolve的是一个promise实例,p2状态由p1的状态决定
3. 再过2s时,p1状态变为reject,会导致p2状态变为reject,被catch捕获
5. reject()
- 将promise对象的状态由:pending变为rejected
- 在异步操作失败时调用,并将异步操作爆出的错误,通过参数传递出去
- 参数通常是(Error对象的实例)
6. 注意:resolve和reject函数并不会终止promise参数函数的执行,所以一般都会加上return防止后面的语句继续执行
7. then() ------------------- 返回值是一个新的promie实例对象,后面可以接着调用then方法 ----------
- promise实例对象生成后,可以then方法分别指定resolve状态和reject状态的回调函数
- then方法有两个参数,第一个在状态变为fulfilled时调用,第二个在状态变为rejected时调用,第二个参数可选
- then()方法的两个参数函数的 (参数 )是promise对象传出的值
- then()定义在原型上,作用是添加状态改变时的回调函数
- (then)方法返回的是:(新的promise实例对象),不是原来的promise实例
- 注意:
- 1. then方法的参数是状态改变后的回调函数,回调函数的参数是promise状态改变时抛出的值
- 2. 但是then方法有返回值,返回的是一个新的promise实例,可以链式调用then,此时后面的then的参数是前面then的返回值
- 3. 如果前面then返回的是promise对象,则后面的then的回调函数将在前面then状态改变后执行
8. catch ------------------- 返回值是一个新的promie实例对象,后面可以接着调用then方法 ----------
- 指定发生错误的回调函数
- 是 .then(null, rejection)或.then(undefined, rejection)的别名
- 能捕获promise中抛出的错误,也能捕获then方法中抛出的错误
- catch返回的是一个promise实例
9. finally
- 指定promise对象最后的状态无论为何,都会执行的操作
- finally指定的回调函数,不接受任何参数,表明finally函数里面的操作与promise状态无关,不依赖promise执行的结果
- 注意:
- finally返回的是一个promise实例对象,并且是前面的值
10. all
- 用于将多个promise实例,包装成一个新的promise实例
- 参数是一个数组(如果不是数组,必须是具有Iterator接口的数据类型),成员是promise实例
- 如果参数数组的成员不是promise实例,会先调用Promise.resolve()将其转换成promise后在进一步处理
-
const p = Promise.all([p1, p2, p3]);
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
-
注意:
1. 如果promise实例自己定义了catch方法,该promise实例中抛出的错误不会被promise.all的catch捕获
2. 因为promise自己的catch方法返回的是一个新的promise实例,catch中没有抛错的话,到promise.all()时的参数数组的成员是这个新的promise实例
3. 所以Promise.all()参数数组中的都是resolove状态
4. 如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
11. race
- 将多个promise实例包装成新的promise实例
-
const p = Promise.race([p1, p2, p3]);
1. 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。
2. 如果参数数组成员不是一个promise,则会先调用Promise.all()转成promise实例
12. allSettled ------------------- 返回值是一个新的promie实例对象
- settled:结束
- 将多个promise实例包装成新的promise实例
- 参数是一个数组,成员是promise实例
- 在所有promise实例状态都改变后(不管是fulfilled还是rejected),包装实例才会结束
- 该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消
----------
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
解析:
1. Promise.allSettled返回的是新的promise实例,状态只可是 fulfilled
2. 状态改变成fulfilled的监听函数(即then函数)的参数是一个数组,成员都是对象
3. 成员对象中都具有status属性,值是 'fulfilled'或者'rejected'
4. 成员对象中,当是 fulfilled时,具有value属性
5. 成员对象中,当是 rejected时,具有reason属性
6. 使用场景:
- 有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。
- Promise.all()不能保证所有操作都结束后在执行某些操作,因为Promise.all()的rejected状态是谁先rejected,则状态就变为rejected
13. any // ----- (和all相反) (和all相反) (和all相反)
- Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。
- 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态
- 所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
- Promise.any()抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。
它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。
14. resolve
- 参数
1. 参数是一个 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2. 参数是一个thenable对象(指的是具有then方法的对象)
- 会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法
3. 参数不是具有then方法的对象,或根本就不是对象
- Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
4. 不带有任何参数
- Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
15. try
- Promise.try
- //
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
手写promise
class MyPromise {
constructor(executor) {
if(typeof executor !== 'function') {
throw new TypeError(` Promise resolver ${executor} is not a function`)
}
this.init()
// 这里用try...catch是因为
// 在promise的参数函数中的错误需要被then中的第二个回调函数所捕获,或者被catch函数所捕获
// 如果在promise的参数参数中抛出错误,就用catch捕获,用this.reject函数去改变状态和存储拒因
try {
executor(this.resolve, this.reject)
} catch(err) {
this.reject(err)
}
}
init = () => {
this.status = MyPromise.PENDING // 状态
this.value = null // 终值
this.reason = null // 拒因
this.onFulfilledCallbacks = [] // 成功回调数组
this.onRejectedCallbacks = [] // 失败回调数组
}
resolve = (value) => {
// 状态的改变,成功回调的执行
if(this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 用于异步的情况
}
}
reject = (reason) => {
// 状态的改变,失败回调的执行
if(this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
then = (onFulfilled, onRejected) => {
// 两个参数都是可选的
// 如果不是函数则被忽略
if(typeof onFulfilled !== MyPromise.Function) {
// 如果第一个参数不是函数,就把第一个参数设置成函数,该函数返回成功时的终值
// 并且该函数要在状态变为fulfilled时执行,参数就是终值
onFulfilled = value => value
}
if(typeof onRejected !== MyPromise.Function) {
// 如果第二个参数不是函数,就把第二个参数设置成函数,该函数返回失败时候的拒因
// 并且该函数要在状态变为fulfilled时执行,参数就是终值
onRejected = reason => {
throw reason
}
}
// 注意:
// then方法要实现链式调用,必须返回一个新的实例,如果是原来的promise实例
// then方法返回一个新的promise实例,即promise2
let promise2 = new Promise((resolve2, reject2) => {
if(this.status === MyPromise.FULFILLED) {
// 这里用setTimeout的原因是:执行顺序
// 如果不用定时器,then方法会立即执行,则不是微任务的表现
setTimeout(() => {
try {
const x = onFulfilled(this.value)
MyPromise.resolvePromise(promise2, x, resolve2, reject2)
} catch(err2) {
reject2(err2)
}
}, 0)
}
if(this.status === MyPromise.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
MyPromise.resolvePromise(promise2, x, resolve2, reject2)
} catch(err2) {
reject2(err2)
}
}, 0)
}
if(this.status === MyPromise.PENDING) {
// 当promise的参数函数中有异步操作时,then方法会优先于resolve()或者reject()先执行
// 这样就是导致执行then()方法时,状态是pending状态
// 这是需要用一个数组来存储将来才会执行的onFulfilled函数
// 这里push进onFulfilledCallbacks的函数,将在resolve()函数中去执行
this.onFulfilledCallbacks.push(value => {
setTimeout(() => {
// 这里之所以还要用setTimeout是因为在成功的回调函数中,resolve()后面还有同步代码的话,要保证把同步执行完,在去执行resolve函数
// 如下:
// console.log('4')
// resolve('5')
// console.log('6')
// 要保证 4 6 5这样的顺序
try {
const x = onFulfilled(value)
MyPromise.resolvePromise(promise2, x, resolve2, reject2)
} catch(err2) {
reject2(err2)
}
})
})
this.onRejectedCallbacks.push(reason => {
setTimeout(() => {
try {
const x = onRejected(reason)
MyPromise.resolvePromise(promise2, x, resolve2, reject2)
} catch(err2) {
reject2(err2)
}
})
})
}
})
return promise2
}
}
MyPromise.PENDING = 'PENDING'
MyPromise.FULFILLED = 'FULFILLED'
MyPromise.REJECTED = 'REJECTED'
MyPromise.Function = 'function'
MyPromise.resolvePromise = function(promise2, x, resolve, reject) {
// x 与 promise 相等
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise'))
}
let called = false
if (x instanceof MyPromise) {
// 判断 x 为 Promise
x.then(
value => {
MyPromise.resolvePromise(promise2, value, resolve, reject)
},
reason => {
reject(reason)
}
)
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// x 为对象或函数
try {
const then = x.then
if (typeof then === 'function') {
then.call(
x,
value => {
if (called) return
called = true
MyPromise.resolvePromise(promise2, value, resolve, reject)
},
reason => {
if (called) return
called = true
reject(reason)
}
)
} else {
if (called) return
called = true
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
Promise.defer = Promise.deferred = function() {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = MyPromise
https://github.com/dream2023/blog/tree/master/promise
then方法没有传参时
- 需要重写 onFulfilled函数,返回当前的终值 this.value
- 需要重写 onRejected函数,返回当前的拒因 this.reason,并抛出
then = (onFulfilled, onRejected) => {
if (typeof onFulfilled !== this.FUNCTION) {
// 没传onFulfilled参数,就重写该函数
// 将调用参数原样返回
// 这里没有直接写 (typeof onFulfilled !== 'function') 防止魔法字符串
onFulfilled = value => value
}
if (typeof onRejected !== this.FUNCTION) {
// 没传onRejected参数,就重写该函数
// 抛出reason
onRejected = reason => {
throw reason
}
}
if (this.status === this.FULFILLED) {
// 是fulfilled状态是,才执行onFulfilled函数,参数是当前的终值
// 即状态改变时为成功时,添加的回调函数
// 这里传参和没有传参都会执行,没传参是执行重写过后的onFulfilled
onFulfilled(this.value)
}
if (this.status === this.REJECTED) {
onRejected(this.reason)
}
}
如何保证执行顺序1
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
resolve(1)
}).then(() => console.log(4))
console.log(3)
问题:如何保证执行顺序是 1234
解决:说明then方法中的代码要异步执行,用定时器模拟解决
说明:如果不用定时器,执行顺序是1243
then = (onFulfilled, onRejected) => {
if (typeof onFulfilled !== this.FUNCTION) {
// 没传onFulfilled参数,就重写该函数
// 将调用参数原样返回
// 这里没有直接写 (typeof onFulfilled !== 'function') 防止魔法字符串
onFulfilled = value => value
}
if (typeof onRejected !== this.FUNCTION) {
// 没传onRejected参数,就重写该函数
// 抛出reason
onRejected = reason => {
throw reason
}
}
if (this.status === this.FULFILLED) {
// 是fulfilled状态是,才执行onFulfilled函数,参数是当前的终值
// 即状态改变时为成功时,添加的回调函数
// 这里传参和没有传参都会执行,没传参是执行重写过后的onFulfilled
// 用定时器解决代码的执行顺序
// 用定时器保证then方法中的参数函数是在同步代码之后执行
setTimeout(() => onFulfilled(this.value), 0)
}
if (this.status === this.REJECTED) {
setTimeout(() => onRejected(this.reason), 0)
}
}
如何保证执行顺序2
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => resolve())
// 当这里有异步操作时,上面的代码打印只有 123,注意 4 并未打印
// 原因是then()方法在resolve()方法前执行了,因为resolve是异步的,导致 then() 中的状态还是 pending 状态
// 而在then方法中并为添加状态是pending状态时的相关操作
}).then(() => console.log(4))
console.log(3)
问题:打印出了123,但是并未打印4
分析:
1. 原因是then()方法在resolve()方法前执行了,因为resolve是异步的,导致 then() 中的状态还是 pending 状态
2. 而在then方法中并为添加状态是pending状态时的相关操作
解决:
1. 在then()方法中添加pending状态下的相关判断
- 并向 onFulfilledCallbacks 数组中push一个方方法,该方中去调用 onFulfilled 方法,参数是当前的value
- 并向 onRejectedCallbacks 数组中 push 一个方法,该方中去调用 onRejected 方法,参数是当前的reason
2. 在resolve()方法中去循环 onFulfilledCallbacks 数组,并执行里面的函数,实参是 this.value
2. 在reject()方法中去循环 onRejectedCallbacks 数组,并执行里面的函数,实参是 this.reason
then = (onFulfilled, onRejected) => {
...
if (this.status === this.PENDING) {
// pending状态push函数到onFulfilledCallbacks数组
this.onFulfilledCallbacks.push(value => onFulfilled(value))
this.onRejectedCallbacks.push(reason => onRejected(reason))
}
}
resolve = (value) => {
if (this.status === this.PENDING) {
this.status = this.FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 执行数组中的函数,并传入实参
}
}
reject = (reason) => {
if (this.status === this.PENDING) {
this.status = this.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
如何保证执行顺序3
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => {
resolve()
console.log(4)
})
}).then(() => console.log(5))
console.log(3)
问题:上面代码输出 12354 , 而真正的promise应该输出 12345
分析:因为resolve()后面还有同步代码,要保证后面的同步代码先执行
解决:在向 onFulfilledCallbacks数组中push方法时,要再用 setTimeout包装,让resolve()后面的代码先执行
then = (onFulfilled, onRejected) => {
...
if (this.status === this.PENDING) {
this.onFulfilledCallbacks.push(value => {
setTimeout(() => { // 再用setTimeout包装,保证resolve()后面的代码先于 then的回调函数 执行
onFulfilled(value)
}, 0)
})
this.onRejectedCallbacks.push(reason => {
setTimeout(() => {
onRejected(reason)
}, 0)
})
}
}
then的链式调用
- 返回一个新的promise
- 新的promise中参数函数的resolve的是onFufiled函数执行后返回的值
then = (onFulfilled, onRejected) => {
if(typeof onFulfilled !== 'function') {
onFulfilled = (value) => value
}
if(typeof onRejected !== 'function') {
onRejected = (reason) => {
throw reason
}
}
const promise2 = new Promise((resolve, reject) => {
if (this.status === Promise.FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value) // 将onFulfilled函数的返回值作为resolve()的参数,传给新的 then() 方法
resolve(x) // promise2的resolve的时机
} catch(err) {
reject(err) // promise2的reject的时机
}
})
}
if (this.status === Promise.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (err) {
reject(err)
}
})
}
if (this.status === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
try {
const x = onFulfilled(value)
resolve(x)
} catch(err) {
reject(err)
}
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const x = onRejected(reason)
resolve(x)
} catch(err) {
reject(err)
}
})
})
}
})
return promise2
}
Promise.all()
------ 和 Promise.any()相反
- 注意是静态方法,用于将多个promise实例包装成一个 (
新的promise实例
) - 参数:(
数组或者具有Iterator接口的数据类型,并且返回的每个成员都是promise实例
)
参数是一个数组,成员是promise实例
如果不是promise实例,会先调用Promise.resolve()转化成promise实例 - 状态
所有数组成员promise都变成 fulfilled时,才会变成 fulfilled
只有一个成员的promise状态变成rejected,就会变成rejected
说明:
1. Promise.all()返回的是一个新的promise,即可以使用then获取resolve和reject的结果
2. 参数是一个数组或者具有Iterator接口的数据
3. 如果参数数组成员不是promise,就会被Promise.resolve()转成promise对象
4. resolve的时机是所有参数成员都变成fulfilled状态时
5. reject的时机是只要有一个rejected状态时
Promise.all = (promises) => {
// 返回一个新的promise实例
return new Promise((resolve, reject) => {
const arr = []
let count = 0 // 记录fulfilled状态的promise个数
const promiseArr = Array.from(promises) // 参数除了数组还可以是具有Iterator接口的数据类型
const len = promiseArr.length
for (let i = 0; i < len; i++) {
Promise.resolve(promiseArr[i]).then(value => { // 如果参数不是promise,会调用Promise.resolve()转成promise
count ++ // 进入这里,表示成功的回调,即fulfilled状态
arr[i] = value // 将该成功的promise装进数组
if (count === len) {
console.log(count, 'count')
resolve(arr)
// 如果count和数组总长度相等,说明都是fulfilled状态了
// 所有resolve的时机就是所有都变成fulfilled状态是resolve
}
}, reject)
}
})
}
const a = Promise.resolve(1)
const b = Promise.resolve(2)
const c = new Promise(resolve => {
setTimeout(() => {
resolve(33)
})
})
Promise.all([a, b, c]).then(value => console.log(value, 'value'))
https://segmentfault.com/a/1190000012820865
https://juejin.im/post/5d0da5c8e51d455ca0436271
Promise.race()
Promise.race = (promises) => {
return new Promise((resolve, reject) => {
const promiseArr = Array.from(promises)
const len = promises.length
for(let i = 0; i < len; i++) {
Promise.resolve(promiseArr[i]).then(value => {
resolve(value) // 直接resolve第一个then是成功时的回调函数接收到的终值
})
}
})
}