Promise
案例引导
调用一个函数,这个函数中发送网络请求(定时器模拟),如果网络请求成功,告知调用者成功,并返回相应的数据,如果请求失败,告知调用者请求失败并返回错误信息
function requestData(url, successCallback, errCallback) {
setTimeout(() => {
if (url === 'coderwhy') {
const data = '我是返回回来的数据'
successCallback(data)
} else {
let err = '我是请求发生错误的错误信息'
errCallback(err)
}
}, 1000)
}
requestData('coderwhy', (res) => {
console.log(res);
}, (err) => {
console.log(err);
})
这样一种异步回调的处理方式有什么缺点呢?
第一,我们必须自己去命名成功和失败的回调函数名称,每个人都不一样。
第二,这样的方式不利于维护,如果有多个网络请求之间相互依赖,那么就会造成回调地狱。
那么有没有一种统一的回调方式去处理这种情况呢,比如不管怎么样,requestData函数都return一个东西,这个东西里面包含了不管是成
功还是失败所需要的信息,我们只要调用这个东西对应的方法就可以拿到我们想要的信息,这种方式就是Promise。
Promise是期约的意思,意思是承诺不管什么情况,我都会给你一个结果。
什么是promise
- Promise是异步编程的一种解决方案,在ES6之前,我们执行异步代码往往需要得到一个回应,这个时候我们会定义回调函数,
- 但是因为这种回调的形式没有一个统一的形式
- 而promise的出现统一了回调的这种形式,并且Promise能够很好的解决毁掉地狱的问题
- 首先Promise是一个类,使用的时候通过new关键字创建Promise的实例,创建实例的时候需要传入一个执行函数(executor)。
- 这个执行函数会在实例创建的时候立即执行,并且异步代码写在执行函数内。另外executor也需要两个参数函数,resolve和reject
- resolve方法会在异步代码成功的时候调用,catch方法会在异步代码失败的时候调用。
- 当调用resolve方法时,会执行Promise实例的then方法传入的回调
- 当调用reject方法时,会执行Promise实例的catch方法传入的回调
用Promise重构案例
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url === 'coderwhy') {
const res = '我是请求回来的数据'
resolve(res)
} else {
const err = 'err message'
reject(err)
}
}, 1000)
})
}
requestData('coderwhy')
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
Promise的三种状态
使用Promise时,会有三种状态,分别是pending(悬而未决),fulfilled(已敲定),rejected(已拒绝)。
- 当执行executor时(没有调用resolve和reject函数),处于pending状态
- 当执行resolve函数时,处于fulfilled状态
- 当执行reject函数时,处于rejected状态
- 状态一经决定就不能修改,也就是说在调用resolve函数之后,处于fulfilled状态,之后再调用reject函数,也不会处于rejected状态,反过来也一样
new Promise((resolve, reject) => {
console.log('pending状态'); //pendding状态
resolve('我是结果') // fulfilled状态
reject('我是错误信息') // rejected状态
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
resolve函数的参数
我们调用resolve函数的时候,会给函数传入一些参数,有以下几种情况:
- 当传入的参数是普通值或者普通对象时,promise的状态由自己决定
new Promise((resolve, reject) => {
resolve('成功')
})
.then(res => {
console.log('1:', res);
})
.catch(err => {
console.log('1:', err);
})
- 当传入的参数是另一个Promise的时候,那么promise的状态由传入的promise的状态决定
const newPromise = new Promise((resolve, reject) => {
// resolve('成功')
reject('失败')
})
new Promise((resolve, reject) => {
resolve(newPromise)
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
- 当传入的参数是一个thenable对象时(实现了then方法),会执行该对象的then方法,并根据then方法的resolve和reject来决定该promise的状态
new Promise((resolve, reject) => {
const thenObj = {
then(resolve, reject) {
resolve('成功')
}
}
resolve(thenObj)
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
实例方法-then方法
then方法是Promise实例对象的方法,放在Promise.prototype原型对象上
- 接收两个参数
then方法其实可以接收两个参数,一个是fulfilled的回调,一个是rejected的回调
//之前我们写fullfiled的回调和rejected的回调是分开写在then和catch里面
new Promise((resolve, reject) => {
resolve('成功')
}).then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
//其实也可以统一写在then里面:
new Promise((resolve, reject) => {
resolve('成功')
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
- 多次调用
Promise实例的then方法可以多次调用,执行结果都一样
const promise = new Promise((resolve, reject) => {
resolve('成功')
})
//第一次调用
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
//第二次调用
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
- then方法第一个回调函数的返回值:
不管该回调函数返回什么值,内部都会重新创建一个新的promise,然后将返回的值作为resolve函数的参数。
情况一:返回值为普通值
then方法的第一个回调函数也可以有返回值,当返回了一个普通值的时候,会把这个普通值包裹在一个新的Promise实例上,并调用该实例的resolve方法,把该值作为参数
const promise = new Promise((resolve, reject) => {
resolve('成功')
})
promise.then(() => {
return 'aaaa' //这里内部会 new Promise 然后把aaaa作为该实例的resolve函数的参数传递,
}).then(res => { //所以这里then方法实际上是上面的新Promise实例的resolve函数执行的回调,
console.log(res); // aaa
})
情况二:返回值为一个Promise实例
即使当then的回调函数返回一个Promise的实例,内部也还是会创建一个新Promise的实例,然后把Promise的实例作为新Promise的resolve的参数传递
那这就回到了我们之前讲过的,如果一个Promise的resolve函数的参数为Promise实例,那么这个Promise的状态将由这个参数Promise来决定。
const promise = new Promise((resolve, reject) => {
resolve('成功')
})
promise.then(() => {
return new Promise((resolve, reject) => {//这里直接返回了一个promise,但是其实内部还是会重新创建一个promise实例,然后把返回的promise作为新创建的promise的resolve函数的参数传递
resolve('成功哒哒哒')
})
}).then(res => { //所以这里then方法实际上是上面的新Promise实例的resolve函数执行的时候的回调
console.log(res); //哒哒哒
})
情况三:返回一个thenable对象
同样的道理,如果返回一个thenable对象,那么会将该thenable对象作为新promise实例的resolve方法的参数传递。而之前也说过,resolve方法的参数如果是一个thenable对象
那么会调用该thenable对象的then方法。
const promise = new Promise((resolve, reject) => {
resolve('成功')
})
promise.then(() => {
return {
then(resolve, reject) {
reject('错误')
}
}
}).then(undefined, err => {
console.log(err); //错误
})
实例方法-catch方法
catch方法也是Promise实例对象上的一个方法,也是放在Promise的原型上的 Promise.prototype上
- 多次调用
和then方法一样,catch方法也可以多次调用,多次调用结果一样
const promise = new Promise((resolve, reject) => {
reject('err message')
})
promise.catch(err => {
console.log(err);
})
promise.catch(err => {
console.log(err);
})
promise.catch(err => {
console.log(err);
})
- 抛出异常
当在executor中调用reject函数时会执行catch方法,exectuor内代码发生错误也会执行catch函数
const promise = new Promise((resolve, reject) => {
throw new Error('err message')
})
promise.catch(err => {
console.log(err);
})
- catch的写法
一起看看catch有几种写法:
const promise = new Promise((resolve, reject) => {
reject('err message')
})
//第一种:
promise.catch(err => {
console.log(err);
})
//第二种:
promise.then(undefined, err => {
console.log(err);
})
//第三种:
promise.then(res => {
return 111
}).catch(err => {
console.log(err); // err message
})
仔细研究一下第三种写法,这里打印的err必然是promise调用reject函数传递进来的 ‘err message’,但是如果then方法的第一个回调里面也返回了promise
并且也调用了reject方法,那这个时候的catch打印的会是谁的错误信息呢?
const promise = new Promise((resolve, reject) => {
reject('err message')
})
promise.then(res => {
return new Promise((resolve, reject) => {
reject('inner err message')
})
}).catch(err => {
console.log(err); // err message
})
可以看到打印的依然是err message
那么当第一个promise执行的是resolve函数,但是then方法的第一个参数返回promise并且调用了reject函数呢?
const promise = new Promise((resolve, reject) => {
resolve()
})
promise.then(res => {
return new Promise((resolve, reject) => {
reject('inner err message')
})
}).catch(err => {
console.log(err); // inner err message
})
可以看到,打印的是inner err message,也就是得到一个结论:当使用.then().catch()的形式捕获异常的时候,catch方法会优先捕获外部promise的异常,
如果外部没有异常,而内部有异常的时候,那么会捕获内部promise的异常。
细节题:
const promise = new Promise((resolve, reject) => {
reject('err message')
})
promise.then(res => {
console.log(res);
})
promise.catch(err => {
console.log(err);
})
执行结果:
err message
------------
Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
//这段代码会报错,当然报错和promise.catch方法无关,而是和promise.then方法有关,这是因为promise的executor内部调用了reject函数
//而then方法内部却没有定义对应的回调函数来处理reject()函数的错误信息,所以会报错。
//解决报错的方法就是在then方法内部添加上reject函数的回调:
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
- catch有返回值 then方法里面有返回值,会包裹promise,那catch方法有返回值,同样会包裹promise,并且会调用新promise的resolve方法传递return的值
除非return的值为报错或者抛出异常,才会调用reject方法,否则调用resolve
const promise = new Promise((resolve, reject) => {
reject('err message')
})
promise.then(res => {
console.log(res);
}).catch(err => {
return err //会创建新promise,并用其resolve方法传递err,
}).then(res => {
console.log('res:',res); // 'res:' err message
}).catch(err => {
console.log('err:',err);
})
实例方法-finally方法
finally是不管promise处于fulfilled状态还是rejected状态都会执行的方法,通常用来做一些清除工作,finally的回调函数不接收参数。
const promise = new Promise((resolve, reject) => {
// resolve('success')
reject('err happend ')
})
promise.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
}).finally(() => {
console.log('finally happend');
})
类方法-resolve和reject方法
- Promise.resolve()
如果我们想把一个对象转换成promise,可能会这样:
const obj = { name: 'why' }
const promise = new Promise((resolve, reject) => {
resolve(obj)
})
但是这样写有点麻烦,我们可以直接使用Promise.resolve()方法,将一个值或对象直接变成promise,上面的代码等同于:
const promise1 = Promise.resolve(obj)
promise1.then(res => {
console.log(res);
})
// 同样,如果Promise.resolve方法的参数是一个promise实例或者是一个thenable对象,那么会依照我们之前讲的三种情况
const promise2 = Promise.resolve(new Promise((resolve, reject) => {
reject('err message')
}))
promise2.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
- Promise.reject()
Promise.reject和Promise.catch的效果一样,不同的是,Promise.catch不会有三种情况,不管传入的是什么,都相当于调用reject方法。
const promise = Promise.reject('err message')
promise.then(undefined, err => {
console.log(err); //err message
})
类方法-all和allSettled
- all方法
现在有这种情况:有三个promise,假如我们想让这三个promise的状态都处于fulfilled状态时,拿到它们的结果,就可以使用Promise.all方法
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success2')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success3')
}, 3000)
})
Promise.all([p1, p2, p3, 'abc']).then(res => {
console.log(res); //[ 'success1', 'success2', 'success3', 'abc' ]
})
1.all方法接收一个数组参数,里面放想要fulfilled的promise,如果数组中有普通值,那么会调用Promise.resolve()将其转换成promise
2.all方法的返回值也是一个promise,可以通过then方法拿到返回的结果,结果也是一个数组,里面包含了每个promise的resolve结果
3.并且结果的顺序和p1p2p3的顺序有关
4.假如中间有一个promise处于rejected状态,那么Promise.all就会变成rejected状态
- allSettled方法
all方法有一个缺陷,当任何一个promise返回rejected,那么all会立即变成rejected,那么其他处于fullfilled状态的promsie结果我们获取不到
如果我们想让三个promise的状态不管是fulfilled还是rejected状态,都把它们的状态返回,就可以使用Promise.allSettled
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success3')
}, 3000)
})
Promise.allSettled([p1, p2, p3, 'abc']).then(res => {
console.log(res);
})
//结果:
[
{ status: 'fulfilled', value: 'success1' },
{ status: 'rejected', reason: 'err' },
{ status: 'fulfilled', value: 'success1' },
{ status: 'fulfilled', value: 'abc' }
]
1.allSettled同样也返回一个promise,这个promise始终是fulfilled状态,并且通过then拿到结果,结果是一个数组,
2.数组中是一个个对象,里面表示了每一个promise的状态,以及resolve或reject状态的结果
类方法-race和any
- race方法
假如我们想让多个promise中,只要有其中一个promise先返回结果,就立即拿到这个结果。就可以使用Promise.race()
如果先返回结果的这个promise的状态为rejected,那么race的状态也变成rejected
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 3000)
})
Promise.race([p1, p2, p3]).then(res => {
console.log(res); //success
}).catch(err => {
console.log(err);
})
- any方法
any是ES2022新增的一个方法,与race不同的是,如果先返回的promise是rejected状态,那么any也会等到有一个promise的结果返回fulfilled状态的时候,
把这个fulfilled状态的promsie的状态返回,除非所有的promise都返回rejected状态,那么race会返回rejected状态
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 3000)
})
Promise.any([p1, p2, p3]).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
全都返回rejected状态:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err2')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err3')
}, 3000)
})
Promise.any([p1, p2, p3]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // All promises were rejected
})