含义
首先,明白「promise 是异步编程的解决方案」。
可以理解为是一个“保存着未来才会结束的事件(通常是一个异步操作)的结果”的容器。
从语法上,Promise是一个构造函数,它的实例是对象。
特点
1、三种常见状态:
pending(进行中)、fulfilled(已成功)、rejected(已失败)
2、promise对象状态不受外界影响,只有异步操作的结果可以决定是哪种状态。
3、promise对象状态一旦改变,就不会再变。
只有两种可能:pending -> fulfilled、pending -> rejected
一旦发生,状态就凝固了。称为 resolved(已定型)。
缺点
1、Promise一旦创建无法取消。
2、不设置回调函数,得不到promise内部抛出的错误。(下述catch中会详细解释)。
3、penging 状态时,无法感知进行到哪一阶段(刚开始or快结束)。
用法
基础语法
1、参数:function(resolve,reject){}
const timeOut = new Promise(function(resolve,reject){
setTimeout(resolve, 10, 'done1')
})
timeOut.then(function(value){
console.log(value)
})
// 'done'
- resolve,reject 可选
- setTimeout的第三个(及以上)参数,会给第一个函数当入参
- resolve的参数会传给 回调函数
2、执行顺序
const timeOut = new Promise(function(resolve,reject){
console.log('1')
setTimeout(resolve, 10, 'done1')
console.log('2')
})
timeOut.then(function(value){
console.log(value)
})
console.log('3')
// ‘1’
// ‘2’
// ‘3’
// ‘done’
- promise一旦创建后,会立即执行。然后当前脚本的所有同步任务执行完才会执行回调函数。
- 调用reslove、reject后,promise不会结束执行。所以通常在此时调用return
3、resolve的参数 是 promise时,其状态由返回的promise决定;
reject 参数 是promise时,其状态不受影响。
const p1 = new Promise (function(resolve, reject){
setTimeout(reject('p1 error'), 1000)
})
const p2 = new Promise (function(resolve, reject){
resolve(p1)
})
p2.then(function(value){
console.log('success:',value)
},function(error){
console.log('error:',error)
})
// ‘error:p1 error’
// 虽然p2的状态是resolve,但由于返回的是另一个promise p1,其状态在1s后便无效,更新为 p1的状态
const p1 = new Promise (function(resolve, reject){
setTimeout(resolve('p1 success'), 10)
})
const p2 = new Promise (function(resolve, reject){
reject(p1);
})
p2.then(function(value){
console.log('success:',value)
},function(error){
console.log('error:',error)
})
// ‘error:’,Promise {<resolved>: "p1 success"}
//如果 p2状态是rejected,p1无论咋搞都没用
Promise.prototype.then()
1、then 方法 第一个参数是 resolved状态时的回调函数,第二个参数是rejected状态时的回调函数。都可选。
2、then 方法返回的 仍是 promise实例。所以可以采用链式写法。
链式then,可以指定一组按照次序执行的异步操作。
前一个回调函数有可能返回一个promise对象,后一个回调函数就会等待该 promise对象的状态发生变化,才会被调用。(具体参考拓展用法2)
const p1 = new Promise (function(resolve, reject){
setTimeout(resolve('p1 success'), 10)
})
const p2 = new Promise (function(resolve, reject){
resolve('p2 success')
})
p2.then(function(value){
console.log('success:',value)
return p1
},function(error){
console.log('error:',error)
return error
}).then(function(v){
console.log(v)
})
//'success: p2 success'
//‘p1 success'’
Promise.prototype.catch()
1、调用reject,异步操作抛出错误(throw new Error(‘test’)),都会被catch方法捕获
2、在resolve后面抛出错误,不会被捕获。(因为promise状态一旦被改变就不会再变)
3、promise错误会一直向后传递,直到被捕获。(这个捕获包括then的第二个参数,catch方法)
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
// 因为第一个状态reject,第二个同样,直到被捕获
});
一般来说,不要在then() 方法里定义 reject状态的回调函数(then的第二个参数),最好用catch方法
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// 用下面的,不要用上面的
promise
.then(function(data) {
// success
})
.catch(function(err) {
// error
});
4、”promise 会吃掉错误”。
就是说,在promise内部发生错误,程序不会退出进程,终止脚本执行
const p2 = new Promise (function(resolve, reject){
resolve(x+2)
})
p2.then(function(value){
console.log('success:',value)
},function(error){
console.log('error:',error)
return error
}).catch(function(error){
console.log('catch error',error)
})
setTimeout(() => { console.log(123) }, 2000);
//error: ReferenceError: x is not defined
// 123
浏览器运行到错误代码那行,会打印错误提示,但promise内部错误不会影响promise外部的代码。
这里就是上述提到的 缺点2
5、catch 返回一个新的promise,可以接着 then() or catch()
Promise.prototype.finally()
1、不管promise对象最后的状态如何都会执行的操作。
2、finally 的回调函数不接受任何参数,所以不能在此处感知promise的状态。
const p2 = new Promise (function(resolve, reject){
reject('p2 error')
})
p2.then(function(value){
console.log('success:',value)
}).finally(function(v){
console.log('finally',v)
})
// finally undefined
Promise.all()
将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3]);
1、p 的状态由 p1、p2、p3决定:
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3有一个被rejected,p的状态就变成rejected。此时第一个被rejected的实例的返回值,传给p的回调函数
2、详细解读上述两条原则:
多个promise实例rejected时,第一个作为返回值,指的执行快的的第一个 ```javascript const p2 = new Promise (function(resolve, reject){ reject(‘p2 error’) }) const p1 = new Promise (function(resolve, reject){ // setTimeout(reject(‘p1 error’), 10000) 这种延时无效,待查 setTimeout(()=>{reject(‘p1 error’)}, 10000) }) const p3 = new Promise(function(resolve){ setTimeout(resolve(‘p3 success’,2000)) })
Promise.all([p1 , p2, p3]).then((result)=>{ console.log(‘all result’,result) }).catch((error)=>{ console.log(‘all catch:’, error) })
//all catch: p2 error
//按理说应该时p2先的,不知道为什么 //确实是p2,因为上述延时无效
2. 如果某个promise实例自己catch错误后,则对于Promise.all收到的就是fulfilled
```javascript
const p1 = new Promise (function(resolve, reject){
setTimeout(resolve('p1 success'), 10)
})
const p2 = new Promise (function(resolve, reject){
reject('p2 error')
}).catch((error)=>{
console.log('p2 catch:', error)
})
const p3 = new Promise(function(resolve){
setTimeout(resolve('p3 success',2000))
})
Promise.all([p1 , p2, p3]).then((result)=>{
console.log('all result',result)
}).catch((error)=>{
console.log('catch:', error)
})
// p2 catch: p2 error
// all result ["p1 success", undefined, "p3 success"]
//不难理解,p2 catch 后其返回的也是一个promise,(因为catch的返回值也是promise)此时的状态时fulfilled,所以就执行了resolved的回调函数。
//如果此时catch返回个值,也会被all 接收到
const p2 = new Promise (function(resolve, reject){
reject('p2 error')
}).catch((error)=>{
console.log('p2 catch:', error)
return false
})
// p2 catch: p2 error
// all result ["p1 success", false, "p3 success"]
//是的 false也是返回值
Promise.race()
同样是将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
race的用例:如果指定时间没有获得结果,就将promise的状态变为rejected,否则变回resolved。拓展用法1替代写法。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(console.log).catch(console.error);
Promise.allSettled()
这个方法像是针对 all 方法提出的更全面功能的用法。大家都知道,all接收的promise里一旦有一个变成了 rejected,整个状态都变成了rejected,且拿不到其他 fulfilled状态的promise值(当然,你可以把所有的promise都加上catch转为 resolved,但麻烦)。
现在 allSettled(),ES2020新引入,它可以等所有的异步操作都结束了,不管是rejected 还是 fulfilled状态,然后拿到异步操作的结果组成一个数组,再执行下一步。
const p1 = new Promise (function(resolve, reject){
setTimeout(()=>{resolve('p1 success')}, 1000)
})
const p2 = new Promise (function(resolve, reject){
reject('p2 error')
})
const p3 = new Promise(function(resolve){
setTimeout(()=>{resolve('p3 success')},2000)
})
Promise.allSettled([p1,p2,p3]).then((result)=>{
console.log('allSettled result:',result)
})
Promise.any()
ES2021加入any 方法。
这个方法像是针对all方法 提出的反向用法。
- 只要参数实例有一个变成 fulfilled状态,包装实例就会变成fulfilled 状态。
- 只有所有实例变成rejected状态,包装实例才会变成rejected状态。
Promise.resolve()
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
拓展用法
1、解决某些异步长时间pengding无法进行下一步问题,使用promise和setTimeout再封装一个promise
fetchLocation(){
return new Promise((resolve, reject) => {
let timeOut;
window.tpPlus.getLocation().then(res => {
if(res.success){
clearTimeout(timeOut);
resolve(res)
}else{
clearTimeout(timeOut);
reject(res)
}
})
timeOut = setTimeout(() => {
alert('打开定位,可快速查看')
resolve('超时')
},1000)
})
}
2、解决多个异步操作递进调用避免嵌套问题。
async submit(){
const { formVals } = field.getValues();
return new Promise((resolve,reject) => {
// 实名认证
recognitionIdCard(formVals)
.then(() => {
//实人认证
return faceAuth()
})
.then(() => {
return submitApi(formVals)
})
.then(() => {
Toast('提交成功');
resolve()
})
.catch((error) => {
reject(error)
})
})
}
tips: 感谢@noldor(noldor)提供的实践代码
promise相关文档
剖析 Promise 内部结构,一步一步实现一个完整的、能通过所有 Test case 的 Promise 类
最简实现 Promise,支持异步链式调用(20 行)