引入
系统学习ES6各种特性,了解背后的原理。
本章记录Promise
笔记
1.1 Promise的含义
Promise
是异步编程的一种解决方案,比传统的回调函数、事件更加合理且强大。最早由社区提出并实现,ES6将其写进了语言标准并统一了用法,原生提供Promise
对象。
Promise
是一个容器,其中保存着某个未来才会结束的事件(异步操作)的结果。
Promise对象有三种状态:
- Pending 进行中
- Fulfilled 已成功
- Rejected 已失败
只有异步操作的结果可以决定当前是哪一种状态,一旦状态改变(只可能从Pending变为Fulfilled或Rejected)后就不再变化,此后任何时候都能从中得到相应的结果。
这与事件不同,事件的特点是在发生时错过了,再去监听是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套回调(回调地狱),此外Promise对象提供的解构也使得控制异步操作更加容易。
Promise也有一些缺点:
- 无法取消Promise,一旦新建就会立即执行
- 若不设置回调函数(
.catch()
等操作),Promise内抛出的错误不会反应到外部 - 处于Pending状态时,无法得知当前进展到哪一阶段(刚刚开始还是即将完成)
1.2 基本用法
Promise对象为构造函数,其接受一个函数作为参数来生成Promise实例,该函数有两个参数resolve
和reject
,是两个函数,作用分别是将该Promise实例的状态由Pending转换为Fulfilled和Rejected。
let pro = new Promise((resolve, reject)=>{
// 异步操作...
// 注意下列代码应当在异步操作的 回调 中执行
if (/*xxx异步操作完成后,成功了*/){
resolve(val);
}else{
reject(err);
}
});
Promise实例生成后可以通过实例的then
方法分别指定Resolved和Rejected状态的回调函数,其中Rejected回调函数是可选的,这两个函数都接受Promise对象传出的值(成功的值、失败的值)为参数。
注意:Rejected回调函数相当于抛出错误,需要有错误回调进行拦截才能进行处理,否则会被忽略
与传统try catch
不同,错误不会被传递到外层代码中,虽然浏览器会打印该错误,但外部代码不会受到影响
pro.then(val=>{}, err=>{});
注意:新建Promise实例时,传入构造函数的函数内容会立即执行!而且是同步(阻塞进程的)操作
(所以不要把一堆同步操作放到Promise实例的构造里,没有什么意义)
let pro = new Promise((resolve, reject)=>{
console.log(1)
resolve();
}).then(res=>console.log(3))
console.log(2)
//输出 1 2 3
then
方法中的内容会在所有同步任务结束后再执行,所以依次输出1 2 3
注意:调用resolve()
, reject()
并不会终结参数函数的运行
new Promise(resolve=>{
resolve(1);
console.log(2);
}).then(res=>console.log(res));
\\ 输出 2 1
因为then中的代码会在本轮同步任务结束后才执行,所以还是会先输出2
一般来说执行完resolve或者reject方法后,该Promise的任务就完成了,应当终止函数的运行,所以可以在其见面加上return
,避免意外情况。如:return resolve(1);
但是注意,resolve()
后再抛出错误,Promise的状态不会改变为已失败,并且会忽略抛出的错误,因为Promise状态一经改变就不能再更改
1.3 Promise.prototype.then()
Promise实例具有then方法,该方法返回的是一个新的Promise实例,该实例和前一个实例不一样
新的Promise实例是一个状态由then方法内回调函数决定的Promise对象:
- 返回了值或者没有返回任何值,新Promise对象为已成功,并且值为该值或者
undefined
- 抛出错误,新Promise对象为拒绝状态,并且将该错误作为值
- 返回已成功的Promise,新Promise对象也是已成功,那个Promise的值作为新Promise对象的值
- 返回已失败的Promise,新Promise对象也是已失败,那个Promise的值作为新Promise对象的值
- 返回Pending的Promise,新Promise对象也是Pending,并且它的终态与那个Promise的终态相同,最终值也相同
由于then方法返回新Promise对象,所以可以进行链式调用
pro.then(res=>someAsyncFunc(res))
.then(res=>someAsyncFunc(res))
.then(res=>console.log(res))
\\此处then方法中使用箭头函数,返回了someAsyncFunc(),也就是返回Pending的Promise
1.4 Promise.prototype.catch()
Promise.prototype.catch(rejection)
是Promise.prototype.then(null, rejection)
的别名
因为Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止(但不会传递到外层)
所以实际应用中往往使用.then(success).catch(rejection)
来接收成功与失败回调,没有成功或者失败时会跳过对应的回调处理。
pro.then(res=>someAsyncFunc(res))
.then(res=>someAsyncFunc(res))
.then(res=>console.log(res))
.catch(err=>console.log(err))
//此处的catch可以处理之前任意一处发生的错误(发生错误后其他的then不会执行)
注意,catch()
返回的还是一个Promise对象(和then一样),因此后面还可以接着调用then等等
1.5 Promise.all()
该方法用于将多个Promise实例包装成一个新的Promise实例。
该方法接受一个可迭代对象,如数组,作为参数。
let p = Promise.all([p1, p2, p3])
其中p1, p2, p3都是Promise实例,若不是则会调用Promise.resolve()
将其转换为Promise实例
而p的状态由p1, p2, p3决定:
- 当三个Promise对象都变为Fulfilled时,p变为Fulfilled,值为三者值组成的数组
- 但三者中任意一个成为Rejected,p变为Rejected,值为三者中第一个成为Rejected的值
注意:若其中的Promise对象自己定义了catch()
方法拦截错误,那么错误不会被传递到此处,catch返回的也是一个新的Promise作为all方法的参数内容,所以无影响
1.6 Promise.race()
该方法和all()
很相似,只是新Promise实例的状态与参数中状态的关系不同
let p = Promise.race([p1, p2, p3])
只要p1, p2, p3中有一个实例率先改变状态,那么p的状态就跟着改变,并获得那个实例的值。
应用技巧:超时错误
const p = Promise.race([
fetch('I resource-that-may-take- a - while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000) //5s后抛出错误
})
])
p.then(res=>console.log(res))
.catch(err=>console.log(err))
//若5s内fetch结束,则p的状态会因为setTimeout抛出的错误而改变
1.7 Promise.resolve()
该方法可以将现有对象转化为Promise对象,例子如下
let p = Promise.resolve('foo');
\\等价于
let p = new Promise(resolve=>resolve('foo'));
- 参数是Promise对象时,该方法不做任何修改,直接返回原对象
- 参数是thenable(即具有then方法的对象),该方法会先将其转化为Promise对象然后立即执行其then方法
let thenable = {
then: (resolve, reject)=>{
console.log(1)
resolve(2);
}
}
let p =Promise.resolve(thenable)
// 先输出1,然后p由Pending转化为Fulfilled且值为2
- 参数是其他情况,返回一个新的Promise实例,状态为Fulfilled,值为参数本身(当无参数时,值为undefined)
1.8 Promise.reject()
该方法也返回一个新的Promise实例,状态为Rejected
但是不同于Promise.resolve
,不论参数是什么类型,该方法都会把参数原封不动的作为新Promise对象的值
1.9 有用的附加方法
1.9.1 done()
无论Promise对象的链式调用以then方法还是catch方法结尾,只要最后一个方法内再次抛出错误,外部代码都无法将其捕获。
为此可以实现一个done方法,其总是处于链式调用的最后一个,既能作为最后一个then方法使用,又能保证向外抛出最终的错误
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected) //此处的this指向调用done方法的对象
.catch(function (err) { //实现关键:额外添加一个catch用来抛出可能出现的错误
// 抛出一个全局错误
setTimeout(() => { throw err }, 0);
});
};
1.9.2 finally()
该方法用于指定不管Promise实例最终状态如何都会执行的操作。
Promise.prototype.finally = function (callback) {
let P = this.constructor; //构造函数,指向原生Promise对象
// 并且将callback()返回值转化为Promise对象返回,从而续接到then链式调用中
return this.then(
val=>P.resolve(callback()).then(()=>val),
err=>P.resolve(callback()).then(()=>{throw err})
);
};