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

news2024/12/30 4:29:18

theme: fancy

一,前言

上篇,主要实现了 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 参考资料

image.png

  • .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:

// 执行结果:直接返回Promise
Promise {
  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

MDN 参考资料

image.png

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;


维护记录

  • 20211104
    • 添加 finally 6 种情况的测试分析及结论;
  • 20211105
    • 添加 MDN 链接和截图;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WebDAV之葫芦儿·派盘+纯纯写作

纯纯写作 支持WebDAV方式连接葫芦儿派盘。 推荐一款简单的纯文本编辑软件,纯粹、有安全感、随时、绝对不丢失内容、具备良好的写作体验。界面非常简洁,且功能齐全,旨在给用户们提供贴心、舒适的写作服务。纯纯写作可以实现在Windows桌面PC, Android设备上同步的移动办公软件…

梦熊杯-十二月月赛-钻石组-B.卷王

B. Problem B.卷王(study.cpp) 内存限制&#xff1a;256 MiB 时间限制&#xff1a;1000 ms 标准输入输出 题目类型&#xff1a;传统 评测方式&#xff1a;文本比较 题目描述&#xff1a; 在某个群里&#xff0c;有一个卷王&#xff0c;他很关心群友的学习成绩。 他扬言…

Web API请求查询字符串超长问题

调用Web API时返回“HTTP Error 404.15 - Not Found”的错误提示&#xff0c;如下截图&#xff1a; 经检查&#xff0c;传递的查询参数字符长度超过IIS默认最大查询字符串长度的设置&#xff0c;IIS服务器的 maxQueryString&#xff08;单位&#xff1a;字节&#xff09; 设置&…

一杯白酒搅动的资本江湖

王家卫的电影《东邪西毒》中有句经典台词&#xff1a;酒越喝越暖&#xff0c;水越喝越寒。江湖刀光剑影&#xff0c;唯有酒让武林人士在奔波途中心中一暖。然而&#xff0c;在白酒搅动的资本江湖中&#xff0c;酒企已经很久没有感受到上市的“暖意”了。自2016年金徽酒挂牌上交…

网络基础2--HTTP协议详解

目录 一、自定制协议 二、TCP粘包问题 2.1. 定长结构体和非定长结构体在发送时的区别 2.2. 那么为什么内存不连续的结构体不能直接使用send发送呢&#xff1f; 2.2. 那我们怎样去接收不定长的数据呢&#xff1f; 2.3. 我们怎样去接收不连续的内存呢&#xff1f; 2.4序列化和…

shiro权限框架介绍以及springboot整合shiro

What is Apache Shiro? Apache Shiro is a powerful and flexible open-source security framework that cleanly handles authentication, authorization, enterprise session management and cryptography. Apache Shiro’s first and foremost goal is to be easy to use…

LDO的dropout voltage

目录从一个设计错误谈起Dropout压降从芯片内部电路结构理解dropout压降MOS管 > 低dropout压降的LDO进一步降低dropout压降的方法在系统设计中&#xff0c;电源管理是不可或缺的&#xff0c;而LDO&#xff08;线性稳压器&#xff09;是电源管理器件中及其重要的一类器件。其应…

【JUC系列】线程变量ThreadLocal详解

ThreadLocal简介 ThreadLocal叫做线程变量&#xff0c;意思是ThreadLocal中填充的变量属于当前线程&#xff0c;该变量对其他线程而言是隔离的&#xff0c;也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本&#xff0c;那么每个线程可以访…

Java-基础-2.常用类

一&#xff1a;object 他是java中最基础&#xff0c;最核心的类。在java类中&#xff0c;为申明extends&#xff0c;默认 extends Object。方法。 3.1 toString方法 public String toString()方法&#xff0c;其返回值是 String 类型&#xff0c;描 述当前对象的有关信息。 3.…

Java中的多态及应用示例

Java中的多态及实现示例简介实现示例instance of&#xff08;待补&#xff09;补入Java多态 简介 【预留&#xff08;业务逻辑&#xff09;接口的抽象类】对象 new 【实现了该 预留&#xff08;业务逻辑&#xff09;接口】的【预留业务逻辑接口的抽象类的子类】的 对象&…

Java_Git:5. 在IntelliJ IDEA中使用git

目录 1 在Idea中配置git 2 将工程添加至git 2.1 创建工程 2.2 创建本地仓库 2.3 将工程添加至本地仓库 2.4 推送到远程 3 从远程仓库克隆 4 从服务端拉取代码 1 在Idea中配置git 安装好IntelliJ IDEA后&#xff0c;如果Git安装在默认路径下&#xff0c;那么idea会自动找…

C语言学习笔记-数据类型

在 C 语言中&#xff0c;数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间&#xff0c;以及如何解释存储的位模式。 C数据可以从两方面宽泛的看&#xff1a;一、变量和常量&#xff1b;二、数据类型 从定义出发&#xff0c;变…

打造数字时代的“诺亚方舟”

玛雅人关于2012世界末日的预言除了提供给好莱坞灵感&#xff0c;拍摄了令人震撼的灾难片《2012》之外&#xff0c;似乎没有留下什么&#xff0c;人们已经渐渐淡忘了世界末日的说法。 但现实世界中却接二连三地在发生着重大灾难&#xff1a;2018年巴西国家博物馆大火&#xff0c…

(考研湖科大教书匠计算机网络)第三章数据链路层-第四节:可靠传输

专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;可靠传输基本概念&#xff08;1&#xff09;不可靠传输与可靠传输&#xff08;2&#xff09;分组丢失、分组失序和分组重复&#xff08;3&#xff09;可靠传输注意二&#xff1…

如何快速掌握Mybatis-Plus

目录 1. 什么是mybatis-plus 2. 初体验 3. 日志 4. 主键生成策略 6. 自动填充 7. 乐观锁 8. 条件查询 9. 分页查询 10. 逻辑删除 11. Wrapper 12. 在Mybatis-plus中使用xml配置 1. 什么是mybatis-plus MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff…

细说Linux虚拟化KVM-Qemu之virtio驱动

说明&#xff1a; KVM版本&#xff1a;5.9.1QEMU版本&#xff1a;5.0.0工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 前篇文章讲完了Qemu中如何来创建Virtio Device&#xff0c;本文将围绕Guest OS中的Virtio Driver来展开&#xff1b; 看一下Guest OS&…

发布自己的依赖(代码)到maven仓库2023

如果不想看文字可以看下面的视频 发布自己代码到maven中央仓库&#xff01;第一步去sonatype注册登录&#xff0c;并发布issue https://issues.sonatype.org/secure/Dashboard.jspa > 去这个网站注册登录&#xff0c;用户名和密码会在后面发布jar包到中央仓库上用的到。 …

力扣刷题记录——748. 最短补全词、744. 寻找比目标字母大的最小字母、747. 至少是其他数字两倍的最大数

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《力扣刷题记录——748. 最短补全词、744. 寻找比目标字母…

LinuxC—网络套接字

网络套接字socket 1 跨主机传输需要注意的问题 1.1 字节序问题 大端存储与小端存储 大端&#xff1a;低地址处方高字节小端&#xff1a;低地址处方低字节 主机字节序和网络字节序 若两个主机的字节序存储方式不同&#xff0c;直接传输的数据被对方接收后会就会使完全错误的&a…

Java8到Java17之间的主要特性描述

Java8到Java17之间的主要特性描述 文章目录Java8到Java17之间的主要特性描述Java8lambda表达式与Stream API方法引用&#xff08;Method Reference&#xff09;接口默认方法&#xff08;Default Methods&#xff09;类型注解&#xff08;Type Annotations&#xff09;可重复注解…