深入理解Promise
Promise的前提概念
Promise是一个构造函数,用来生成Promise实例
Promise
构造函数接受一个函数作为参数,该函数有两个参数,分别是resolve
和reject
resolve:成功时的回调
reject:失败时的回调
Promise分别有三个状态
1、pending :进行中
2、fulfilled:已成功
3、rejected:已失败
Promise的执行
Promise
一旦新建后就会立马执行,无法中途取消,Promise
返回的只有两种状态,并且该状态产生就无法更改。也就是说Promise
的状态只有以下两种情况:
1、从pending
到fulfilled
2、从pending
到rejected
pending
的状态一旦改变,就不会再发生变化。
Promise的结果返回
Promise
内部结果一旦返回,resolve
或reject
其中的任何一种,即可产生回调。
1、resolve
的回调在then()
中获取(成功的回调)
2、reject
的回调在cathc()
中获取(失败,捕获错误)
let promise = new Promise(function(resolve,reject){
// sth...
let i = 5;
if( i > 1){
resolve(i) // 成功
} else {
reject(error) // 失败
}
})
promise.then(el=>{
// el即为resolve的参数
console.log(el); // 5
})
.catch(err=>{
console.log(err);
})
Promise的执行顺序
Promise
创建后会立马执行,then()
是一个异步回调,将在所有的同步任务执行完后执行then()
,也就是说:Promise
在新建的时候会立即执行,then()
或catch()
为异步任务,当所有同步任务执行完后才会执行then()
let pro = new Promise(function(resolve,reject){
console.log('Promise新建');
resolve()
})
pro.then(el=>{
console.log('.then回调');
})
console.log('同步任务');
上述代码的执行结果:
1、Promise新建
2、同步任务
3、then()回调
Promise参数和执行顺序
resolve
函数的参数出了可以携带正常值以外,还可以携带Promise
实例
let pro = new Promise(function(resolve,reject){
let i = 10
if(i < 50){
resolve(i)
} else {
reject(error)
}
})
let pro2 = new Promise(function(resolve,reject){
resolve(pro) // 参数为上一个Promise实例
})
pro2.then(el=>{
console.log(el); // 10
})
.catch(err=>{
console.log(err);
})
执行顺序:
先执行pro
,如果pro
的状态是pending
时,pro2
则会等待执行,当pro
的状态变成resolve
或rejected
时,pro2
则会立马执行,由于pro
决定pro2
的状态,所以后面的then
变成了针对pro
Promise的嵌套执行
let p = new Promise((resolve,reject)=>{
let i = 10
if(i > 5){
resolve(i)
} else {
reject('pro错误')
}
})
let p2 = new Promise((resolve,reject)=>{
p.then(el=>{
console.log(el * 2); // 20
if(el > 6){
// 此处需要写成return resolve(el)看起来会更正常
resolve(el)
console.log('我是p1的.then回调'); // 我是p1的.then回调
} else {
reject('pro2错误')
}
})
})
let p3 = new Promise((resolve,reject)=>{
p2.then(el=>{
if(el > 50){
resolve(el)
} else {
reject('pro3错误')
}
})
})
多个Promise
的执行顺序:
新建Promise
后会立马执行,先执行Promise
内的任务,执行完成后再执行then()
如果Promise
内有其它Promise
的then()
任务执行(这里简称p2
),会先将p2
的then()
任务执行完毕,再执行自身的then()
任务
多个Promise
并行,会先按顺序执行Promise
的任务,再分别按照顺序分别执行他们的then()
任务
需要
注意
的是:上面p2
的Promse
中,正常来说,resolve
或reject
执行完后,Promise
的使命应该完成,但此处因为执行顺序的问题,并没有先执行resolve
,而是执行了console
的任务,因该写成return resolve(value)
的形式,让Promise
更正常一些,否则会出现不必要的意外。
Promise.prototype.then()
then
方法是定义在原型对象Promise.prototype
上的,它可以为Promise
实例添加状态改变时的回调函数。
then
有两个参数(可选):
resolved
:成功状态的回调函数
rejected
:失败状态的回调函数
then
返回的是一个新的Promise
实例,因此可以采用链式写法,then()
后面还可以接then()
let p = new Promise((resolve,reject)=>{
let i = 5;
if(i > 4){
resolve(i)
}
})
p.then(el=>{
console.log('then1:'+ el);
if(el > 3){
return el
}
}).then(el=>{
console.log('then2:'+ el * 2);
}).catch(err=>{
console.log(err);
}
/*
输出结果:
then1:5
then2:10
*/
上面代码使用then
方法,第一个then()
回调函数执行完后,会将返回结果传入第二个回调函数,也就是说,第二个then()
的参数是第一个then()
返回的结果。
catch
的捕获具有冒泡性质,也就是说,前面不论.then
了多少次,里面发生的错误,都会被最后一个catch
捕获到。
Promise.prototype.catch()
Promise.prototype.catch()
方法是.then(null,rejection)
的别名,也就是说,catch
其实是用来指定发生错误时的回调函数。
let p = new Promise((resolve,reject)=>{
reject('错误信息')
})
p.then(el=>{
// sth...
}).catch(err=>{
console.log(err); // 错误信息
})
上述代码中,p
方法返回Promise
对象,该对象返回的是reject
,then()
方法调用回调函数,then()
有两个参数,一个resolved
和rejected
,如果Promise
返回的是reject
,那么.then
的状态就会变成rejected
,rejected
会调用catch()
方法指定的回调函数,处理该错误,catch
的参数就是reject
抛出的错误信息。
如果then运行中抛出错误,也会被catch()捕获
let p2 = new Promise((resolve,reject)=>{
resolve(100)
})
p2.then(el=>{
throw new Error('then抛错')
}).catch(err=>{
console.log(err); // Error: then抛错
})
catch捕捉错误的方式
// 方式一
let promise = new Promise(function(resolve,reject){
try {
throw new Error('方式一错误')
} catch(e){
reject(e)
}
})
promise.catch(err=>{
console.log(err); // 方式一错误
})
// 方式二
let promise2 = new Promise(function(resolve,reject){
reject(new Error('方式二错误'))
})
promise2.catch(err=>{
console.log(err); // 方式二错误
})
// 方式三
let promise3 = new Promise((resolve,reject)=>{
reject('方式三错误')
})
promise3.catch(err=>{
console.log(err); // 方式三错误
})
以上三种效果是一样的,前两种通过Error
抛出异常,后一种直接抛出异常
catch
返回的也是一个Promise
对象,后面还可以接着调用then()
方法
如果Promise
的状态变成resolved
,那么再抛出错误是无效的,因为Promise
的状态一旦发生改变,就会永久保持该状态。
执行阻塞问题
Promise
内部错误不会影响到到异步队列中的Promise
外部代码,同步代码还是会造成阻塞
let p3 = new Promise((resolve,reject)=>{
resolve(str) // ReferenceError: str is not defined
})
p3.then(el=>{console.log(el)})
setTimeout(()=>{console.log('Promise外部代码')},1000) // Promise外部代码
console.log('同步代码'); // 未打印
Promise
在建立时会立即执行,此处报错了,所以后面的同步代码都无法执行,但是异步的setTimeout
还是会执行,并不受影响。
finally()
finally
有最后、终于的意思,也就是说finally()
方法不管Promise
最后的状态如何,都会执行
let p = new Promise((resolve,reject)=>{
resolve('东方不败')
})
p.then(el=>{
console.log(el); // 东方不败
})
.catch(err=>{
console.log(err);
})
.finally(()=>
console.log('finally执行') // finally执行
)
上述代码中,不管Promise
最后的状态怎么样,finally
都会执行,即使没有then()
和catch()
,finally
也会执行
需要注意的是,
finally
不接受任何参数,但是finally
的执行顺序跟内部是否是一个函数有关,如果finally
内部是一个函数,那么finally
会在Promise
最后执行,如果finally
内部没有任何函数,直接输出,那么finally
会在Promise
最前面执行。
finally
的执行跟Promise
的状态无关。
Promise.all()
Promise.all()
方法用于将多个Promise
实例包装成一个新的Promise
实例
语法:let p = Promise.all([p1,p2,p3])
let p = new Promise((resolve,reject)=>{
resolve(10)
})
p.then(el=>{
console.log(el);
})
let p2 = new Promise((resolve,reject)=>{
// resolve(20)
reject(20)
})
p2.then(el=>{
console.log(el);
})
.catch(err=>{console.log('p2:',err);})
// p、p2都返回resolve则触发该函数
Promise.all([p,p2])
.then(el=>{
console.log(el);
})
.catch(err=>{console.log('all:',err);}) // all:20
当Promise.all
的两个promise
参数状态都变成fulfilled
时,才会触发Promise.all()
的方法,如果其中以一个参数被rejected
,那么Promise.all()
也会变成rejected
的状态,会被catch
捕捉。
上面代码中,如果p2
没有chach
方法,则会调用Promise.all()
的catch
race()
Promise.race()
方法可以将多个Promise
实例包装成一个新的Promise
实例。
语法: let p = Promise.race([p1,p2,p3])
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p1状态')
},2000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p2状态')
},1000)
})
let p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('p3状态')
},3000)
})
let p4 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('reject:p4状态')
},4000)
})
let promise = Promise.race([p,p2,p3,p4])
promise
.then(el=>{
console.log(el); // p2状态
})
.catch(err=>{
console.log(err);
})
race
有竞争,角逐的意思,该方法其实就是参数中,哪一个Promise
的状态先发生改变,就调用先发生改变的promise
,这个el
就是最先发生改变的promise
的返回值。
Promise.allSettled()
Promise.allSettled()
方法可以在所有异步操作都结束的情况下,调用该方法。它与Promise.all()
方法不同的是,all()
方法需要所有异步都成功才会进入then()
,而allSettled()
不需要。
let p = new Promise((resolve,reject)=>{
resolve('p1状态')
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('p2状态')
},3000)
})
Promise.allSettled([p,p2])
.then(el=> console.log(el) )
Promise.allSettled()
的参数不管是reject
还是resolve
都会触发,只要参数的状态全都发生改变即可,上面代码中,p2
延迟了3s
状态才发生改变,那么Promise.allSettled()
则会等待参数的状态全都发生改变时触发。
Promise.any()
Promise.any()
方法接受一组Promise
实例作为参数,包装成一个新的Promise
实例返回。
let p = new Promise((resolve,reject)=>{
resolve('p1状态')
})
let p2 = new Promise((resolve,reject)=>{
reject('p2状态')
})
Promise.any([p,p2])
.then(el=>{
console.log('then:',el); // then: p1状态
})
.catch(err=>{
console.log('catch:',err);
})
只要有一个参数的状态变成fulfilled
,Promise.any
就会变成fulfilled
状态
如果所有的参数实例都变成rejected
状态,包装实例才会变成rejected
状态
也就是说,any
是只要有一个参数成功,any
就可以then
,所有参数都reject
,any
才会catch
Promise.resolve()
Promise.resolve()
可以将现有对象转为Promise
对象,Promise.resolve()
方法就起到这个作用。
语法:let p = Promise.resolve(obj)
let p = Promise.resolve('obj')
p.then(el=> console.log(el)) // obj
// 其实相当于
let p2 = new Promise(resolve=>resolve('obj'))
如果Promise.resolve()
没有参数,那么将直接返回resolved
状态的Promise
对象,并且是一个没有参数的then()
方法。
let p3 = Promise.resolve()
p3.then(el=>{
console.log(el); // undefined
})
setTimeout(()=>{
console.log('定时器');
},0)
Promise.resolve()
.then(()=>{
setTimeout(()=>{console.log('resolve内定时器')},0)
console.log('resolve内部');
})
console.log('外部');
Promise
的执行顺序,Promise
是一个异步的微任务,同步任务执行完后才会执行异步任务,异步的微任务会先执行,微任务执行完后才会执行异步的宏任务,此处的setTimeout
是异步的宏任务,所以会最后执行,外部的定时器是异步的宏任务,Promise
内部的定时器是异步微任务内的宏任务,所以微任务内的宏任务会更慢执行,此处的执行顺序为:外部、resolve
内部、定时器、resolve
内定时器。
Promise.reject()
Promise.reject()
返回一个新的Promise
实例,该实例的状态为rejected
let p = Promise.reject('错误信息')
p.catch(err=>console.log(err))
// 等同于
let p2 = new Promise((resolve,reject)=>{
reject('错误信息2')
})
p2.catch(err=>console.log(err))
Promise.try()
实际开发中,会遇到这么一种情况,不管函数是同步还是异步的,都想用Promise
来处理它,因为这样就可以用then()
指定下一步流程,catch
处理抛出的错误,更便于管理流程。
基于Promise
我们可以用async
做到同步的代码风格执行异步操作,现在有一个最新的提案,使用Promise.try()
来捕获所有的同步和异步错误。
为什么这么做?
Promise
抛出的错误,catch
可以捕获到,但此处仅限于当前Promise
的异常错误,也就是异步的。如果同步错误抛出,那么catch()
是捕捉不到的,需要在外层再嵌套一层try...catch
,这样写起来非常麻烦,代码如下:
try {
let p = new Promise((resolve,reject)=>{
reject('异步错误')
})
p.catch(err=> void console.log(err) ) // 异步错误
throw Error('同步错误')
} catch (e) {
console.log(e); // 同步错误
}
// 这样catch两次,可以处理同步和异步的抛错问题
如果使用Promise.try()
就可以捕获所有的同步和异步错误
Promise.try(()=>{
let p2 = new Promise((resolve,reject)=>{
reject('异步错误2')
})
throw Error('同步错误2')
})
.then(el=>{
console.log('then:',el);
})
.catch(err=>{
console.log('catch:',err);
})
此处的Promise.try
就相当于try
代码块,Promise.catch
就相当于catch
代码块。
案例源码:https://gitee.com/wang_fan_w/es6-science-institute
如果觉得这篇文章对你有帮助,欢迎点亮一下star哟