JavaScript学习笔记(二十八)期约(二)
期约的实例方法
- 期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。
- 这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。
1、实现Thenable接口
- then()方法被认为实现了Thenable接口:
1 | class MyThenable { |
2、Promise.prototype.then()
- Promise.prototype.then()是为期约实例添加处理程序的主要方法。
- 接收最多两个参数:onResolved处理程序和onRejected处理程序。
- 这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。
1 | function onResolved(id) { |
- 因为期约只能转换为最终状态一次,所以这两个操作一定是互斥的。
- 如果想只提供onRejected参数,那就要在onResolved参数的位置上传入undefined。这样有助于避免在内存中创建多余的对象,对期待函数参数的类型系统也是一个交代。
- Promise.prototype.then ()方法返回一个新的期约实例。
1 | let p1 = new Promise(() => {}); |
- 这个新期约实例基于onResovled处理程序的返回值构建。换句话说,该处理程序的返回值会通过Promise.resolve()包装来生成新期约。如果没有提供这个处理程序,则 Promise.resolve()就会包装上一个期约解决之后的值。如果没有显式的返回语句,则Promise.resolve()会包装默认的返回值undefined。
1 | let p1 = Promise.resolve('foo'); |
- onRejected处理程序也与之类似: onRejected 处理程序返回的值也会被Promise.resolve()包装。
- 拒绝处理程序在捕获错误后不抛出异常是符合期约的行为,应该返回一个解决期约。
1 | let p1 = Promise.reject('foo'); |
3、Promise.prototype.catch()
- 该方法用于给期约添加拒绝处理程序。
- 只接收一个参数:onRejected处理程序。
- 这个方法就是一个语法糖,调用它就相当于调用Promise.prototype.then(null, onRejected)。
- Promise.prototype.catch()返回一个新的期约实例:
1 | let p = Promise.reject(); |
4、Promise.prototype.finally()
- 方法用于给期约添加onFinally处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。
- 可以避免onResolved和onRejected处理程序中出现冗余代码。
- onFinally 处理程序没有办法知道期约的状态是解决还是拒绝,故这个方法主要用于添加清理代码。
- 方法返回一个新的期约实例。
1 | let p1 = Promise.resolve(); |
1 | let p1 = new Promise(() => {}); |
- onFinally被设计为一个状态无关的方法,所以在大多数情况下它将表现为父期约的传递。
- 如果返回的是一个待定的期约,或者onFinally处理程序抛出了错误(显式抛出或返回了一个拒绝期约),则会返回相应的期约(待定或拒绝)。
1 | let p1 = Promise.resolve('foo'); |
非重入期约方法
- 非重入:当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处理程序的代码之后的同步代码一定会在处理程序之前先执行。即使期约一开始就是与附加处理程序关联的状态,执行顺序也是这样的。
1 | // 创建解决的期约 |
- 先添加处理程序后解决期约也是一样的。如果添加处理程序后,同步代码才改变期约状态,那么处理程序仍然会基于该状态变化表现出非重入特性。
1 | let synchronousResolve; |
- 非重人适用于onResolved/onRejected处理程序、catch()处理程序和finally()处理程序。
1 | let p1 = Promise.resolve(); |
6、邻近处理程序的执行顺序
- 如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行。
- 无论是then()、catch()还是 finally()添加的处理程序都是如此。
1 | let p1 = Promise.resolve(); |
7、传递解决值和拒绝理由
- 到了落定状态后,期约会提供其解决值(如果兑现)或其拒绝理由(如果拒绝)给相关状态的处理程序。拿到返回值后,就可以进一步对这个值进行操作。
- 在执行函数中,解决的值和拒绝的理由是分别作为resolve() 和reject() 的第一个参数往后传的。然后,这些值又会传给它们各自的处理程序,作为onResolved或onRejected处理程序的唯一参数。
1 | let p1 = new Promise((resolve, reject) => resolve('foo')); |
- Promise.resolve()和Promise.reject()在被调用时就会接收解决值和拒绝理由。同样地,它们返回的期约也会像执行器一样把这些值传给onResolved或onRejected处理程序。
1 | let p1 = Promise.resolve('foo'); |
拒绝期约与拒绝错误处理
- 拒绝期约类似于throw()表达式,因为它们都代表一种程序状态,即需要中断或者特殊处理。
- 在期约的执行函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。
1 | let p1 = new Promise((resolve, reject) => reject(Error('foo'))); |
- 期约可以以任何理由拒绝,包括undefined, 但最好统一使用错误对象。这样做主要是因为创建错误对象可以让浏览器捕获错误对象中的栈追踪信息,而这些信息对调试是非常关键的。
- 在期约中抛出错误时,因为错误实际上是从消息队列中异步抛出的,所以并不会阻止运行时继续执行同步指令。
- 异步错误只能通过异步的onRejected处理程序捕获;这不包括捕获执行函数中的错误,在解决或拒绝期约之前,仍然可以使用try/catch 在执行函数中捕获错误。
1 | // 正确 |
- then()和catch()的onRejected处理程序在语义上相当于try/catch。
- onRejected处理程序的任务应该是在捕获异步错误之后返回一个解决的期约。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 姚永坤的小窝!
评论