十三. Promise
13.1. 认识Promise
-
Es6引入的语法, js里面的一种异步编程的解决方案, 最常见场景就是网络请求,
-
Promise是一个类,可以翻译成 承诺、许诺 、期约;
-
在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立即执行,并且会传入另外两个回调函数resolve、reject作为executor的参数;
- 成功时;回调resolve函数,会执行Promise对象的then方法传入的回调函数;
- 失败时;回调reject函数,会执行Promise对象的catch方法传入的回调函数;
13.2. 基本使用:
有异步请求代码时, 用promise包裹起来, 当数据请求成功时,调用resolve(), resolve会回调then方法传入的回调函数, 将请求结果传入then处理, 实现请求和处理代码的分离 ; 若请求失败,调用reject, reject会回调catch方法传入的回调函数, 将错误的信息传入catch处理
// 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
new Promise((resolve, reject)=>{
setTimeout(()=> {
resolve('success message') // 数据请求成功; 回调resolve()
// reject('err message') // 数据请求失败时;回调reject()
},1000)
// then方法传入的回调函数,会在Promise执行resolve函数时,被回调
}).then((res) =>{
console.log(res); // success message
// catch方法传入的回调函数,会在Promise执行catch函数时,被回调
}).catch((err)=>{
console.log(err); // err message
})
13.3. 三种状态
状态一旦确定下来,那么就是不可更改的(锁定)
-
待定(pending): 初始状态,既没有被兑现,也没有被拒绝;
- 当执行executor中的代码时,处于该状态;
-
已兑现(fulfilled): 意味着操作成功完成;
- 执行了resolve时,处于该状态;
-
已拒绝(rejected): 意味着操作失败;
- 执行了reject时,处于该状态;
13.4. Promise的resolve参数
- 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
- resolve的参数如果是一个新Promise, 那么这个新Promise会决定原Promise的状态,相当于进行了状态移交
const newPromise= new Promise((resolve, reject)=>{
reject("err message")
})
new Promise((resolve, reject)=>{
resolve(newPromise) // 状态移交后,相当于这里是执行的reject("err message")
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err); // err message
})
3. resolve的参数如果是一个对象,并且对象有实现then方法,那么会执行then方法,并且根据then方法的结果来决定Promise的状态:
new Promise((resolve, reject)=>{
const obj={
then: function(resolve, reject){ reject('err message') }
}
resolve(obj)
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err); // err message
})
13.5. Promise的then方法
then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then
- then方法可以接受两个参数:
- fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
- reject的回调函数:当状态变成reject时会回调的函数;
new Promise((resolve, reject)=>{
setTimeout(()=>{
// resolve('data')
reject('err')
},1000)
}).then(
data=>{
console.log(data);
},
err=>{
console.log(err);
}
)
2. 同一个Promise可以被多次调用then方法
- 当我们的resolve方法被回调时,所有的then方法传入的回调函数都会被调用
const promise = new Promise((resolve, reject) => {
resolve('success message')
})
promise.then(res => {
console.log('res1 ' + res); // res1 success message
})
promise.then(res => {
console.log('res2 ' + res); // res2 success message
})
promise.then(res => {
console.log('res3 ' + res); // res3 success message
})
3. then方法- 返回值
-
then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用:
- 但是then方法返回的Promise到底处于什么样的状态呢?
-
Promise有三种状态,那么这个Promise处于什么状态呢?
-
当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
-
当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
-
注意:如果是err回调函数return 一个值,那么这么值会作为resolve的参数
- 情况一:返回一个普通的值(数值/字符串/对象/undefined);
- 情况二:返回一个Promise;
- 情况三:返回一个thenable值;
-
当then方法抛出一个异常时,那么它处于reject状态;
-
3.1 情况一:
-
如果我们返回的是一个普通值,那么这个普通值会被作为一个新的Promise的resolve值
- 如果没写返回值,默认则返回undefined,undefined会被作为一个新的Promise的resolve值
const promise = new Promise((resolve, reject) => { resolve('success message') }) promise.then(res => { console.log(res); // success message return 1 }).then(res=>{ console.log(res); // 1 }).then(res => { console.log(res); // undefined }) // 上面等同下面代码 // promise.then(res => { // return new Promise((resolve) => { // console.log(res); // resolve(1) // }) // }).then(res => { // console.log(res); // }).then(res => { // console.log(res); // }) // 注意:如果是err回调函数return 一个值,那么这么值会作为resolve的参数 const promise = new Promise((resolve, reject) => { reject('err message') }) promise.then(res => { console.log('err1 ' + err); // err1 err message return 12 }).then(res => { console.log('res2 ' + res); // res2 12 }, err => { console.log('err2 ' + err); })
3.2 情况二:
-
这里依然会创建一个新的Promise,只是新的Promise的resolve参数是旧的Promise,
-
因为resolve参数是Promise,所以新Promise会决定原Promise的状态
const promise = new Promise((resolve, reject) => { resolve('success message') }) promise.then(res => { return new Promise((resolve)=>{ setTimeout(() => { resolve(111) },3000) }) }).then(res => { console.log(res); // 111 }) // 上面如同下面的代码 // const newPromise= new Promise(resolve=>{ // resolve(111) // }) // promise.then(res => { // return new Promise((resolve)=>{ // resolve (newPromise) // }) // }).then(res => { // console.log(res); // 111 // })
3.3 情况三:
-
resolve参数是对象且对象内实现了then方法,那么会执行then方法,并且根据then方法的结果来决定Promise的状态:
const promise = new Promise((resolve, reject) => { resolve('success message') }) promise.then(res => { // 这里还是返回一个新Promise,将对象放到resolve中 return { // return new Promise(resolve=>resolve(obj.then())) then: function (resolve, reject) { resolve(111) } } }).then(res => { console.log(res); // 111 })
13.6. Promise的catch方法
-
当executor抛出异常时,会调用catch方法传入的回调函数
- catch方法其实是语法糖
const promise = new Promise((resolve, reject) => { throw new Error('err message') }) promise.catch(err => { console.log(err); // err message }) // catch方法是下面这种方式的语法糖 // promise.then(undefined,err => { // console.log(err); // err message // }) -
catch的特殊性
-
这里有两个Promise:原Promise / 返回的新Promise;那这里的catch方法哪个Promise的方法?
- catch方法是优先捕获原Promise的异常,如果原Promise没有异常,则捕获新Promise的异常
- 如果新的Promise也没有异常,则catch方法不执行
const promise = new Promise((resolve, reject) => { reject('原Promise err message') }) promise.then(res => { return 111 }).catch(err => { console.log(err); // 原Promise err message })const promise = new Promise((resolve, reject) => { resolve('success message') }) promise.then(res => { throw new Error('新Promise err message') }).catch(err => { console.log(err); // 新Promise err message }) -
-
拒绝捕获的问题
- then和catch分开写就是两个方法了,不会相互影响
- reject()执行时,会回调catch传入的回调参数,但then方法执行时,并没有处理捕获异常的方法
const promise = new Promise((resolve, reject) => { reject('err message') }) promise.then(res => { console.log(res); }) promise.catch(err => { console.log(err); }) -
catch方法- 返回值
- 把catch的返回值,作为一个新的Promise的resolve值
const promise = new Promise((resolve, reject) => { reject('err message') }) promise.then(res => { console.log(res); }).catch(err => { return "aaaa" // 等同new Promise(resolve=>resolve('aaaa') }).then(res => { console.log(res); // aaa }).catch(err => { console.log(err); })
13.7. Promise的finally方法
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。
finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。
13.8. 类方法- resolve
前面的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。
下面我们再来看一下Promise的类方法。
- 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
- Promise.resolve的用法相当于new Promise,并且执行resolve操作:
Promise.resolve({name: "why"})
// 等同于
// new Promise((resolve, reject)=>{
// resolve({ name: "why" })
// })
13.9. 类方法- reject
- reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
- Promise.reject的用法相当于new Promise,只是会调用reject:
- Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
Promise.reject({name: "why"})
// 等同于
// new Promise((resolve, reject)=>{
// reject({ name: "why" })
// })
13.10. 类方法- all
-
all的作用是将多个Promise包裹在一起形成一个新的Promise;
-
新的Promise状态由包裹的所有Promise共同决定:
- 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值 组成一个数组;
- 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数
需求:所有的Promise都变成fulfilled时,再拿结果
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'kebi',
age: 18
});
}, 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'wyh',
age: 19
});
}, 1000)
})
// 如果传入数组的不是Promise,会自动将其转成Promise; Promise.resolve("aaa")
Promise.all([p1, p2, "aaa"]).then((results) => {
// results是一个数组,results[0]是上面的请求结果,results[1]是下面的请求结果
// 0: {name: 'kebi', age: 18}
// 1: {name: 'wyh', age: 19}
// 3: 'aaa'
console.log(results);
})
13.11. 类方法- allSettled
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
- 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
在ES11(ES2020)中,添加了新的API Promise.allSettled:
- 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
- 并且这个Promise的结果一定是fulfilled的;
通过打印的结果可以知道:
- allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
- 这个对象中包含status状态,以及对应的value值;
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'kebi',
age: 18
});
}, 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject({
name: 'wyh',
age: 19
});
}, 1000)
})
// 如果传入数组的不是Promise,也会自动将其转成Promise
Promise.allSettled([p1, p2]).then((results) => {
// [
// { status: 'fulfilled', value: { name: 'kebi', age: 18 } },
// { status: 'rejected', reason: { name: 'wyh', age: 19 } }
// ]
console.log(results);
})
13.12. 类方法- race
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
- race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果(不管是fulfilled还是rejected),那么就使用谁的结果;
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'kebi',
age: 18
});
}, 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'wyh',
age: 19
});
}, 1000)
})
Promise.race([p1, p2]).then((results) => {
// { name: 'wyh', age: 19 }
console.log(results);
}).catch(err=>{
console.log(err);
})
13.13. 类方法- any
any方法是ES12中新增的方法,和race方法是类似的:
-
any方法会等到一个fulfilled状态,才会决定新Promise的状态;
-
如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;再回调catch,然后报一个AggregateError的错误。
- AggregateError是个类,有个errors的属性
- 所有的Promise都是reject时,可以通过err.errors拿到错误信息
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'kebi',
age: 18
});
}, 2000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject({
name: 'wyh',
age: 19
});
}, 100)
})
Promise.any([p1, p2]).then((results) => {
// {name: 'kebi', age: 18}
console.log(results);
}).catch(err => {
console.log(err.errors);
})
13.14. 手写模拟Promise
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
// 工具函数:执行函数时捕获异常
function exeFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
const result = exeFn(value)
resolve(result)
} catch (error) {
reject(error)
}
}
class HYpromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.onRejectedFns = []
this.onFulfilledFns = []
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
// 添加微任务:本轮事件循环结束后就会执行
queueMicrotask(() => {
// 如果状态不是pending则终止执行
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
// debugger
this.onFulfilledFns.forEach(fn => {
fn(this.value)
})
});
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
this.onRejectedFns.forEach(fn => {
fn(this.reason)
})
})
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onRejected = onRejected || (err => {
throw err
})
onFulfilled = onFulfilled || (value => {
return value
})
// 直接返回HYpromise,里面的代码还是会执行,
// 而且可以得到onFulfilled, onRejected的返回值
return new HYpromise((resolve, reject) => {
// 如果then调用时,状态已经确定了,则直接调用对应的回调函数
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
// 如果在res回调函数中抛出异常(throw new Error),则会调用err的回调函数
// try {
// const value = onFulfilled(this.value)
// resolve(value)
// } catch (error) {
// reject(error)
// }
exeFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
exeFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}
// 将成功的回调和失败的回调加到数组里
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) this.onFulfilledFns.push(() => {
// try {
// const value = onFulfilled(this.value)
// resolve(value)
// console.log(this);
// } catch (error) {
// reject(error)
// }
exeFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
});
if (onRejected) this.onRejectedFns.push(() => {
exeFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}
catch (onRejected) {
return this.then(undefined, onRejected)
};
finally(onFinally) {
this.then(() => {
onFinally()
}, () => {
onFinally()
})
}
static resolve(value) {
return new HYpromise(resolve => resolve(value))
}
static reject(reason) {
return new HYpromise((resolve, reject) => {
reject(reason)
})
}
static all(promises) {
// 问题关键:什么时候执行resolve,什么时候执行reject
return new HYpromise((resolve, reject) => {
const values = []
promises.forEach(p => {
p.then(res => {
// debugger
values.push(res)
console.log(values);
if (values.length === promises.length) {
resolve(values)
}
}, err => {
reject(err)
})
})
})
}
static allSettled(promises) {
return new HYpromise(resolve => {
const results = []
promises.forEach(p => {
p.then(res => {
results.push({
status: PROMISE_STATUS_FULFILLED,
value: res
})
if (results.length === promises.length) {
resolve(results)
}
}, err => {
results.push({
status: PROMISE_STATUS_REJECTED,
value: err
})
if (results.length === promises.length) {
resolve(results)
}
})
})
})
}
static race(promises) {
new HYpromise((resolve, reject) => {
promises.forEach(p => {
p.then(res => {
resolve(res)
}, err => {
reject(err)
})
})
})
}
static any(promises) {
// resolve:至少有一个请求成功才执行
// reject:全部请求都失败了才执行
const reasons = []
return new HYpromise((resolve, reject) => {
promises.forEach(p => {
p.then(resolve, err => {
reasons.push(err)
if (reasons.length === promises.length) {
reject(new AggregateError(reasons))
}
})
})
})
}
}
const p1 = new HYpromise((resolve, reject) => {
resolve(1111)
})
const p2 = new HYpromise((resolve, reject) => {
reject(2222)
})
const p3 = new HYpromise((resolve, reject) => {
resolve(3333)
})
HYpromise.race([p1, p2, p3]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
// const promise = new HYpromise((resolve, reject) => {
// reject('reject调用
// resolve('resolve调用')
// })
// HYpromise.resolve('静态resolve').then(res=>{
// console.log(res);
// })
// HYpromise.reject('静态reject').then(err=>{
// console.log(err);
// })
// promise.then(res => {
// console.log(res);
// return '555'
// }).catch(err => {
// console.log(err);
// })
// .finally(fanally => {
// console.log(fanally);
// })
// promise.then(res => {
// console.log(res);
// }).catch(err => {
// console.log(err);
// })
// promise.then(res => {
// console.log('res1: ' + res);
// }, err => {
// console.log('err1: ' + err);
// }
// promise.then(res => {
// console.log('res1: ' + res);
// // throw new Error('456789')
// return 'cunba'
// }, err => {
// console.log('err1: ' + err);
// // throw new Error('456789')
// }).then(res => {
// console.log('res2 ' + res);
// }, err => {
// console.log('err2: ' + err);
// })
// 延时调用then 多次调用
// setTimeout(() => {
// promise.then(res => {
// console.log('res2: ' + res);
// }, err => {
// console.log('err2: ' + err);
// })
// }, 1000);