写点什么

【Promise 源码学习】第十篇 - Promise.prototype.catch 和 Promise.prototype.finally 的实现

作者:Brave
  • 2021 年 12 月 03 日
  • 本文字数:4078 字

    阅读完需:约 13 分钟

一,前言


上篇,主要实现了 Promise 的两个静态 API(类方法):Promise.resolvePromise.reject,主要涉及以下几个点:


  • Promise.resolve 创建并返回一个成功的 promise;

  • Promise.reject 创建并返回一个失败的 promise;

  • Promise.resolve 会等待异步操作完成;Promise.reject 失败会直接返回;


本篇,将实现 Promise 两个实例 API(原型方法):Promise.prototype.catchPromise.prototype.finally


备注:catch 和 finall 是 Promise 使用中的高频 API,在面试题中被重点考察;


二,Promise.prototype.catch


MDN 参考资料



  • .catch 方法会处理 Promise 被拒绝的情况,并返回一个失败的 promise;

  • 实际行为与Promise.prototyope.then(undefined,onRejected)相同;

  • 因此,可以理解为一个只处理了 Promise 失败态的 then 方法;


备注:导致 Promise 失败的情况有:调用了reject()或发生异常;

1,测试原生的 Promise.prototype.catch

Promise.reject(new Promise((resolve, reject)=>{ // 直接  setTimeout(() => {    resolve(200)  }, 1000);})).then((data) => {  // 没有处理失败态,继续到下一个 then 处理  console.log('onFulfilled',data)}).catch(err=>{       // 相当于一个只处理失败态的 then  console.log('onRejected:', err)})
// 执行结果:立即返回 onRejected:Promise { <pending> }
复制代码

2,原理分析


  • Promise.reject 将返回一个失败状态的 promise;

  • 失败的 promise 不会等待异步处理结果返回:内部调用reject()会直接抛出处于 PENDING 态的 promise;

  • 由于 then 函数没有传如onRejected回调处理,将继续到下一个 then 处理;

  • 所以,失败状态的 promise 就会进入到 catch 方法中;

3,功能实现


使用相同代码测试,由于尚未实现原型方法 Promise.prototype.catch,所以会报错;


在 Promise 类中,添加原型方法 catch():

  // 原型方法 catch  // errorFn:失败情况的处理  catch(errorFn){    // 一个只处理失败态的 then    return this.then(null, errorFn)  }
复制代码


测试效果与原生 Promise 一致,直接返回 PENDING 态 Promise:

// 执行结果:直接返回PromisePromise {  state: 'PENDING',  value: undefined,  reason: undefined,  onResolvedCallbacks: [],  onRejectedCallbacks: []} err
复制代码

4,捕获机制


  • try...catch... 中抛出的错误,会被最近的一个catch捕获到,并且不会继续向上冒泡,除非在 catch 中继续 throw 向外部抛出错误;

  • 在 Promise 中抛出的错误,由于被 Promise.prototype.catch 捕获后,返回的仍是一个 promise 对象,因此能够被链式调用,所以,错误会一直传递到最外层;

  • 但是,如果 Promise 抛出错误,但没有使用catch()方法指定错误处理的回调函数,那么,抛出的错误将不会继续向外层代码传递,即:不会有任何反应;


三,Promise.prototype.finally


N 参考资料


1,测试原生的 Promise.prototype.finally

1,成功态 Promise 的 finally


let p = new Promise((resolve, reject)=>{  setTimeout(() => { // 问题 1:finally 是否会等待异步操作执行完成?    resolve(1000)     }, 3000);}).finally(()=>{     // 问题 2:Promise 成功时,是否会进入 finally?  console.log('finally')}).then((data)=>{    // 问题 3:finally 之后,是否可以继续.then?  console.log('then', data)  // 问题 4:then 中的 data 来自哪里?});
// 执行结果: 3 秒后输出 "finally" + "then 1000"
复制代码


分析:

  • finally 方法会等待 Promise 中的异步操作执行完成;

  • Promise 成功时,finally 方法会被执行;(成功失败都执行);

  • finally 执行完成后,可以继续 .then,说明内部返回的是 promise;

  • Promise 成功时,resolve 的内容,将通过 finally 继续透传至下一个 then;


备注: finally 中不能获取到数据 data;

2,失败态 Promise 的 finally


let p = new Promise((resolve, reject)=>{  setTimeout(() => {    reject(1000)  }, 3000);}).finally(()=>{  // 问题 1:Promise 失败时,是否会进入 finally?  console.log('finally')}).then((data)=>{ // 问题 2:Promise 失败时,是否会进入 then,data 来自哪里?  console.log('then', data)}).catch(e=>{     // 问题 3:Promise 失败时,是否会进入 catch,e 来自哪里?  console.log('catch', e)})
// 执行结果:3 秒后输出 "finally" + "catch 1000"
复制代码


分析:

  • Promise 失败时,finally 方法也会被执行;(成功失败都执行)

  • Promise 失败时,reject 的内容,将通过 finally 继续向下传递;(finally 返回失败的 promise,then 没有处理失败,会进入 catch 处理)

3,成功态 Promise 的 finally 中,返回成功 Promise

let p = new Promise((resolve, reject)=>{  setTimeout(() => {    resolve(1000)  }, 3000);}).finally(()=>{  console.log('finally')  return new Promise((resolve, reject)=>{    resolve(2000)  })}).then((data)=>{ // 问题 1:是否会进入 then,data 的值是 1000 还是 2000  console.log('then', data)}).catch(e=>{     // 问题 2:是否会进入 catch,e 的值是 1000 还是 2000  console.log('catch', e)})
// 执行结果:3 秒后输出"finally" + "then 1000"
复制代码


进入了 then,data 值为 1000;finally 使用了上一个 promise 的成功结果;
复制代码

4,成功态 Promise 的 finally 中,返回失败 Promise

let p = new Promise((resolve, reject)=>{  setTimeout(() => {    resolve(1000)  }, 3000);}).finally(()=>{  console.log('finally')  return new Promise((resolve, reject)=>{    reject(2000)  })}).then((data)=>{ // 问题 1:是否会进入 then,data 的值是 1000 还是 2000  console.log('then', data)}).catch(e=>{     // 问题 2:是否会进入 catch,e 的值是 1000 还是 2000  console.log('catch', e)})
// 执行结果:3 秒后输出"finally" + "catch 2000"
复制代码


进入了 catch,e 值为 2000;说明 finally 使用了自己内部 Promise 的失败结果;
复制代码


对比 3,4 两种情况,可得出结论:

  • 当 finally 中的 Promise 成功时,使用上一个 promise 结果返回;

  • 当 finally 中的 Promise 失败时,使用自己的 promise 结果返回;

5,失败态 Promise 的 finally 中,返回成功 Promise

let p = new Promise((resolve, reject)=>{  setTimeout(() => {    reject(1000)     }, 3000);}).finally(()=>{  console.log('finally')  return new Promise((resolve, reject)=>{    resolve(2000)  })}).then((data)=>{ // 问题 1:是否会进入 then,data 的值是 1000 还是 2000  console.log(data)}).catch(e=>{     // 问题 2:是否会进入 catch,e 的值是 1000 还是 2000  console.log('catch', e)})
// 执行结果:3 秒后输出"finally" + "catch 1000"
复制代码


进入了 catch,e 值为 1000;说明 finally 使用了上一个 Promise 的失败结果;
复制代码

6,失败态 Promise 的 finally 中,返回失败 Promise

let p = new Promise((resolve, reject)=>{  setTimeout(() => {    reject(1000)     }, 3000);}).finally(()=>{  console.log('finally')  return new Promise((resolve, reject)=>{    reject(2000)  })}).then((data)=>{ // 问题 1:是否会进入 then,data 的值是 1000 还是 2000  console.log(data)}).catch(e=>{     // 问题 2:是否会进入 catch,e 的值是 1000 还是 2000  console.log('catch', e)})
// 执行结果:3 秒后输出"finally" + "catch 2000"
复制代码


进入了 catch,e 值为 2000;说明 finally 使用了自己 Promise 的失败结果;
复制代码


对比 3,4,5,6 两种情况,可得出结论:

  • 不管 finally 之前面 promise 是成功还是失败,只要 finally 中的 Promise 成功了,就使用上一个 promise 结果作为返回;只要 finally 中的 Promise 失败了,就使用自己的 promise 结果作为返回;

2,原理分析


对以上 6 种情况进行一下总结:

  • finally 是 Promise 的原型方法;

  • finally 中不能够获取到数据 data;

  • finally 内部会继续返回一个 promise;

  • 不管 finally 前的 promise 成功或失败,finally 都会被执行;

  • finally 方法会等待 Promise 中的异步操作执行完成;

  • 【finally 中没有 promise 时】不管 finally 前的 promise 成功或失败,finally 执行完成后,上一个 promise 结果都会继续向下传递;

  • 【finally 中有 promise 时】不管 finally 前的 promise 成功或失败,只要 finally 中 Promise 成功,就返回上一个 promise 结果;只要 finally 中 Promise 失败,就返回自己的 promise 结果;

3,功能实现

1,原型方法、成功失败都执行

// 原型方法Promise.prototype.finally = function(cb){  // finally 内部调用 then 返回 promise  return this.then(()=>{    cb()  // 成功会执行 cb  },()=>{    cb()  // 失败也会执行 cb  })}
复制代码


以上代码实现了:

  • finally 是 Promise 的原型方法;

  • finally 中不能够获取到数据 data;

  • finally 内部会继续返回一个 promise;

  • 不管 finally 前的 promise 成功或失败,finally 都会被执行;

2,等待 Promise 执行完成


finally 内部调用 then,最终会返回 promise,promise 的成功或失败都会执行 cb;


所以,需要等待这个 promise 执行完成;使用具有等待效果的Promise.resolve()进行包装;(注意:Promise.reject()没有等待效果,错误会直接返回)

Promise.prototype.finally = function(cb){  return this.then((data)=>{    return Promise.resolve(cb()).then((dataCb)=>data)  },()=>{    return Promise.resolve(cb()).then((n)=>{throw err});  }) }
复制代码


// todo 代码解析


以上代码实现了:

  • finally 方法会等待 Promise 中的异步操作执行完成;


四,结尾


本篇,主要实现了 Promise 两个实例 API(原型方法):Promise.prototype.catchPromise.prototype.finally,主要涉及以下几个点:


  • Promise.prototype.catch 功能测试、原理分析、源码实现;

  • Promise.prototype.finally 功能测试、原理分析、源码实现;


下一篇,继续实现 Promise 的核心静态 API(类方法):Promise.all;

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Promise 源码学习】第十篇 - Promise.prototype.catch 和 Promise.prototype.finally 的实现