面试官:能不能给 Promise 增加取消功能和进度通知功能... 我:???
扯皮
这段时间闲着没事就去翻翻红宝书,已经看到 Promise 篇了,今天又让我翻到两个陌生的知识点。
因为 Promise 业务场景太多了自我感觉掌握的也比较透彻,之前也跟着 Promise A+ 的规范手写过完整的 Promise,所以这部分内容基本上就大致过一遍,直到看见关于 Promise 的取消以及监听进度...🤔
只能说以后要是我当上面试官一定让候选人来谈谈这两个点,然后顺势安利我这篇文章🤣
不过好像目前为止也没见哪个面试官出过...
正文
取消功能
我们都知道 Promise 的状态是不可逆的,也就是说只能从 pending -> fulfilled 或 pending -> rejected,这一点是毋庸置疑的。
但现在可能会有这样的需求,在状态转换过程当中我们可能不再想让它进行下去了,也就是说让它永远停留至 pending 状态。
奇怪了,想要一直停留在 pending,那我不调用 resolve 和 reject 不就行了🤔
const p = new Promise((resolve, reject) => {setTimeout(() => {// handler data, no resolve and reject}, 1000);});console.log(p); // Promise {<pending>} 💡
但注意我们的需求条件,是在状态转换过程中,也就是说必须有调用 resolve 和 reject,只不过中间可能由于某种条件,阻止了这两个调用。
其实这个场景和超时中断有点类似但还是不太一样,我们先利用 Promise.race
来看看:模拟一个发送请求,如果超时则提示超时错误:
const getData = () =>new Promise((resolve) => {setTimeout(() => {console.log("发送网络请求获取数据"); // ❗resolve("success get Data");}, 2500);});const timer = () =>new Promise((_, reject) => {setTimeout(() => {reject("timeout");}, 2000);});const p = Promise.race([getData(), timer()]).then((res) => {console.log("获取数据:", res);}).catch((err) => {console.log("超时: ", err);});
问题是现在确实能够确认超时了,但 race
的本质是内部会遍历传入的 promise 数组对它们的结果进行判断,那好像并没有实现网络请求的中断哎🤔,即使超时网络请求还会发出:
超时:timeout
而我们想要实现的取消功能是希望不借助 race
等其他方法并且不发送请求。
比如让用户进行控制,一个按钮用来表示发送请求,一个按钮表示取消,来中断 promise 的流程:
当然这里我们不讨论关于请求的取消操作,重点在 Promise 上
其实按照我们的理解只用 Promise 是不可能实现这样的效果的,因为从一开始接触 Promise 就知道一旦调用了 resolve/reject 就代表着要进行状态转换。不过 取消
这两个字相信一定不会陌生,clearTimeout
、clearInterval
嘛。
OK,如果你想到了这一点这个功能就出来了,我们直接先来看红宝书上给出的答案:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><button id="send">Send</button><button id="cancel">Cancel</button><script>class CancelToken {constructor(cancelFn) {this.promise = new Promise((resolve, reject) => {cancelFn(() => {console.log("delay cancelled");resolve();});});}}const sendButton = document.querySelector("#send");const cancelButton = document.querySelector("#cancel");function cancellableDelayedResolve(delay) {console.log("prepare send request");return new Promise((resolve, reject) => {const id = setTimeout(() => {console.log("ajax get data");resolve();}, delay);const cancelToken = new CancelToken((cancelCallback) =>cancelButton.addEventListener("click", cancelCallback));cancelToken.promise.then(() => clearTimeout(id));});}sendButton.addEventListener("click", () => cancellableDelayedResolve(1000));</script></body>
</html>
这段代码说实话是有一点绕的,而且个人觉得是有多余的地方,我们一点一点来看:
首先针对于 sendButton 的事件处理函数,这里传入了一个 delay,可以把它理解为取消功能期限,超过期限就要真的发送请求了。我们看该处理函数内部返回了一个 Promise,而 Promise 的 executor 中首先开启了定时器,并且实例化了一个 CancelToken,而在 CancelToken 中才给 cancelButton 添加点击事件。
这里的 CancelToken 就是我觉得最奇怪的地方,可能没有体会到这个封装的技巧,路过的大佬如果有理解的希望能帮忙解释一下。它的内部创建了一个 Promise,绕了一圈后相当于 cancelButton 的点击处理函数是调用这个 Promise 的 resolve,最终是在其 pending -> fuilfilled,即 then 方法里才去取消定时器,那为什么不直接在事件处理函数中取消呢?难道是为了不影响主执行栈的执行所以才将其推到微任务处理🤔?
介于自己没理解,我就按照自己的思路封装个不一样的🤣:
const sendButton = document.querySelector("#send");
const cancelButton = document.querySelector("#cancel");class CancelPromise {// delay: 取消功能期限 request:获取数据请求(必须返回 promise)constructor(delay, request) {this.req = request;this.delay = delay;this.timer = null;}delayResolve() {return new Promise((resolve, reject) => {console.log("prepare request");this.timer = setTimeout(() => {console.log("send request");this.timer = null;this.req().then((res) => resolve(res),(err) => reject(err));}, this.delay);});}cancelResolve() {console.log("cancel promise");this.timer && clearTimeout(this.timer);}
}// 模拟网络请求
function getData() {return new Promise((resolve) => {setTimeout(() => {resolve("this is data");}, 2000);});
}const cp = new CancelPromise(1000, getData);sendButton.addEventListener("click", () =>cp.delayResolve().then((res) => {console.log("拿到数据:", res);})
);
cancelButton.addEventListener("click", () => cp.cancelResolve());
没啥大毛病捏~
进度通知功能
进度通知?那不就是类似发布订阅嘛?还真是,我们来看红宝书针对这块的描述:
执行中的 Promise 可能会有不少离散的“阶段”,在最终解决之前必须依次经过。某些情况下,监控 Promise 的执行进度会很有用
这个需求就比较明确了,我们直接来看红宝书的实现吧,核心思想就是扩展之前的 Promise,为其添加 notify
方法作为监听,并且在 executor 中增加额外的参数来让用户进行通知操作:
class TrackablePromise extends Promise {constructor(executor) {const notifyHandlers = [];super((resolve, reject) => {return executor(resolve, reject, (status) => {notifyHandlers.map((handler) => handler(status));});});this.notifyHandlers = notifyHandlers;}notify(notifyHandler) {this.notifyHandlers.push(notifyHandler);return this;}
}
let p = new TrackablePromise((resolve, reject, notify) => {function countdown(x) {if (x > 0) {notify(`${20 * x}% remaining`);setTimeout(() => countdown(x - 1), 1000);} else {resolve();}}countdown(5);
});p.notify((x) => setTimeout(console.log, 0, "progress:", x));
p.then(() => setTimeout(console.log, 0, "completed"));
mm 就是这个例子总感觉不太好,为了演示这种效果还用了递归,大伙们觉得呢?
不好就自己再写一个🤣!不过这次的实现就没有多大问题了,基本功能都具备也没有什么阅读障碍,我们再添加一个稍微带点实际场景的例子吧:
// 模拟数据请求
function getData(timer, value) {return new Promise((resolve) => {setTimeout(() => {resolve(value);}, timer);});
}let p = new TrackablePromise(async (resolve, reject, notify) => {try {const res1 = await getData1();notify("已获取到一阶段数据");const res2 = await getData2();notify("已获取到二阶段数据");const res3 = await getData3();notify("已获取到三阶段数据");resolve([res1, res2, res3]);} catch (error) {notify("出错!");reject(error);}
});p.notify((x) => console.log(x));
p.then((res) => console.log("Get All Data:", res));
End
关于取消功能在红宝书上 TC39 委员会也曾准备增加这个特性,但相关提案最终被撤回了。结果 ES6 Promise 被认为是“激进的”:只要 Promise 的逻辑开始执行,就没有办法阻止它执行到完成。
实际上我们学了这么久的 Promise 也默认了这一点,因此这个取消功能反而就不太符合常理,而且十分鸡肋。比如说我们有使用 then 回调接收数据,但因为你点击了取消按钮造成 then 回调不执行,我们知道 Promise 支持链式调用,那如果还有后续操作都将会被中断,这种中断行为 debug 时也十分痛苦,更何况最麻烦的一点是你还需要传入一个 delay 来表示取消的期限,而这个期限到底要设置多少才合适呢...
至于说进度通知功能,仁者见仁智者见智吧...
但不管怎么样两个功能实现的思路都是比较有趣的,而且不太常见,不考虑实用性确实能够成为一道考题,只能说很符合面试官的口味😏
相关文章:

面试官:能不能给 Promise 增加取消功能和进度通知功能... 我:???
扯皮 这段时间闲着没事就去翻翻红宝书,已经看到 Promise 篇了,今天又让我翻到两个陌生的知识点。 因为 Promise 业务场景太多了自我感觉掌握的也比较透彻,之前也跟着 Promise A 的规范手写过完整的 Promise,所以这部分内容基本上…...

详解MySQL增删查改
众所周知,MySQL是非常重要的数据库语言,下面我们来回顾一下mysql的增删查改吧 MySQL创建数据库: CREATE DATABASE 数据库名;MySQL删除数据库: DROP DATABASE <database_name>; --直接删除,不检查是否存在 DROP…...

Mysql开启bin-log日志
目录 一、安装配置 二、mysqlbinlog命令 一、安装配置 yum -y install mariadb mariadb-server#安装mysql数据库#默认配置文件/etc/my.cnfvim /etc/my.cnflog-binmariadb-bin #开启二进制日志 systemctl restart mariadb#会在/car/lib/mysql/产生二进制日志文件࿰…...

Java:性能优化细节01-10
Java:性能优化细节01-10 在Java程序开发过程中,性能优化是一个重要的考虑因素。常见的误解是将性能问题归咎于Java语言本身,然而实际上,性能瓶颈更多地源于程序设计和代码实现方式的不当。因此,培养良好的编码习惯不仅…...

CVE-2022-24652 漏洞复现
CVE-2022-24652 开题 后台管理是thinkphp的,但是工具没检测出漏洞。 登陆后界面如下,上传头像功能值得引起注意 这其实就是CVE-2022-24652,危险类型文件的不加限制上传,是文件上传漏洞。漏洞路由/user/upload/upload 参考文章&a…...

LeetCode、338. 比特位计数【简单,位运算】
文章目录 前言LeetCode、338. 比特位计数【中等,位运算】题目链接与分类思路位运算移位处理前缀思想实现 资料获取 前言 博主介绍:✌目前全网粉丝2W,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java…...

借助Aspose.BarCode条码控件,C# 中的文本转 QR 码生成器
二维码用于在较小的空间内存储大量数据。它们易于使用,可以通过智能手机或其他设备扫描来打开网站、观看视频或访问其他编码信息。在这篇博文中,我们将学习如何使用 C# 以编程方式生成基于文本的 QR 码。我们将提供分步指南和代码片段,帮助您…...

vue打包优化,webpack的8大配置方案
vue-cli 生成的项目通常集成Webpack ,在打包的时候,需要webpack来做一些事情。这里我们希望它可以压缩代码体积,提高运行效率。 文章目录 (1)代码压缩:(2)图片压缩:&…...

B端系统从0到1:有几步,其中需求分析要做啥?
一款B系统从无到有都经历了啥,而其中的需求分析又要做什么?贝格前端工场给老铁们做一下分析,文章写作不易,如果咱们有界面设计和前端开发需求,别忘了私信我呦,开始了。 一、B端系统从0到1都有哪些要走的步骤…...

django中查询优化
在Django中,查询优化是一个重要的主题,因为不正确的查询可能会导致性能问题,尤其是在处理大量数据时。以下是一些在Django中进行查询优化的建议: 一:使用select_related和prefetch_related: select_related用于优化一…...

【JavaScript】输入输出语法
目录 一、输出语法 二、输入语法 一、输出语法 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>D…...

多模态基础--- word Embedding
1 word Embedding 原始的单词编码方式: one-hot,维度太大,不同单词之间相互独立,没有远近关系区分。 wordclass,将同一类单词编码在一起,此时丢失了类别和类别间的相关信息,比如class1和class3…...

Mysql 日志
0 引言 MySQL日志主要分为4类,使用这些日志文件,可以查看MySQL内部发生的事情。这4类日志分别是: ● 错误日志:记录MySQL服务的启动、运行或停止MySQL服务时出现的问题。 ● 查询日志:记录建立的客户端连接和执行的…...

【开源】SpringBoot框架开发服装店库存管理系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…...

云原生之容器编排实践-在K8S集群中使用Registry2搭建私有镜像仓库
背景 基于前面搭建的3节点 Kubernetes 集群,今天我们使用 Registry2 搭建私有镜像仓库,这在镜像安全性以及离线环境下运维等方面具有重要意义。 Note: 由于是测试环境,以下创建了一个 local-storage 的 StorageClass ,并使用本地…...

标准IO 2月4日学习笔记
IO输入输出,操作对象是文件 Linux文件类型: b block 块设备文件 按块扫描设备信息的文件 存储设备 c character 字符设备文件 按字符扫描设备信息的文件 d direct…...

如何在1Panel上偷渡HTTP/3
本文 首发于 Anyeの小站,转载请取得作者同意。 前言 简介 HTTP/3 的基础即谷歌多年探索的基于 UDP 的 QUIC 协议。与 TCP 相比,使用 UDP 可以提供更大的灵活性,并且可以使 QUIC 完全于用户空间中实现——对协议实现的更新不像 TCP 那样需要绑…...

Qt实用技巧:QCustomPlot做北斗GPS显示绝对位置运动轨迹和相对位置运动轨迹图的时,使图按照输入点顺序连曲线
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/136131310 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…...

116 C++ 可变参数函数,initializer_list (初始化列表), 省略号形参
一 可变参数函数 有时候我们传递的参数是不固定的。 这种能接受非固定个数参数的函数就是可变参数函数 怎么实现呢?就要用到 initializer_list 标准库类型 该类型能够使用的前提条件是:所有的实参类型相同。 二,initializer_list(初始化列…...

强国有我社会实践公益活动在合肥市庐阳区开展
2月18日是开工第一天,阳光灿烂、春光明媚。合肥市四十五中2022级星辰(5)班部分同学在监护人的陪伴下来到庐阳区双岗街道万小店社区残疾人工作站,和工作站兄弟姐妹们共同开展“强国复兴有我”社会实践公益活动。合肥市庐阳区为民社…...

Nginx 正向代理、反向代理
文章目录 前言1. 正向代理1.1 概念1.2 逻辑图1.3 使用场景 2. 反向代理2.1 概念2.2 逻辑图2.3 使用场景 前言 正向代理主要是用来解决访问限制问题;反向代理则是提供负载均衡、安全防护等作用 1. 正向代理 1.1 概念 正向代理是一个位于客户端和目标服务器之间的代理…...

软考学习--计算机组成原理与体系结构
计算机组成原理与体系结构 数据的表示 进制转换 R 进制转换为 10 进制–按权展开法 10进制转换为2进制 原码 反码 补码 移码 原码 :数字的二进制表示反码 : 正数的反码等于原码,负数的反码等于原码取反补码: 正数的补码等…...

fish终端下conda activate失败
【问题】fish终端下激活conda环境报错: >> conda activate base CondaError: Run conda init before conda activate ## 然而运行 conda init fish 仍旧无法解决【解决】 参考:https://github.com/conda/conda/issues/11079 方法一…...

FPGA之移位寄存器
SLICEM中的LUT可以配置为32位移位寄存器,而无需使用slice中可用的触发器。以这种方式使用,每个LUT 可以将串 行数据延迟 1 到 32 个时钟周期。移入D (DI1 LUT 引脚)和移出 Q31(MC31 LUT 引脚)线路将LUT级联,以形成更大…...

Android Compose Material3 ModalNavigationDrawer 抽屉的使用(处理了一些坑)
Android Compose Material3 ModalNavigationDrawer 抽屉的使用(处理了一些坑) val drawerState rememberDrawerState(initialValue DrawerValue.Closed) val scope rememberCoroutineScope()ModalNavigationDrawer(drawerState drawerState,drawerC…...

golang select两个channel性能稳定,三个channel时性能会发生抖动,为什么?
golang select两个channel性能稳定,三个channel时性能会发生抖动,为什么? 答题思路 select —> 让 Goroutine同时等待多个 Channel 可读或者可写 —> Goroutine —> 调度器调度 —> 资源竞争 —> 不稳定、抖动 在 Go 中&#…...

VSCODE上使用python_Django
接上篇 https://blog.csdn.net/weixin_44741835/article/details/136135996?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22136135996%22%2C%22source%22%3A%22weixin_44741835%22%7D VSCODE官网: Editing Python …...

探索IDE的世界:什么是IDE?以及适合新手的IDE推荐
引言 在编程的世界里,集成开发环境(IDE)是我们日常工作的重要工具。无论是初学者还是经验丰富的开发者,一个好的IDE都能极大地提高我们的编程效率。那么,什么是IDE呢?对于新手来说,又应该选择哪…...

DoRA(权重分解低秩适应):一种新颖的模型微调方法
来自:小互 DoRA(权重分解低秩适应):一种新颖的模型微调方法 DoRA在LoRA的基础上进一步发展,通过将预训练权重分解为“幅度”和“方向”两个部分进行微调。 这种权重分解方法允许DoRA更精细地控制模型的学习过程&…...

centos7.9 搭建k8s
K3s -轻量级Kubernetes K3s 是轻量级的 Kubernetes。K3s 易于安装,仅需要 Kubernetes 内存的一半,所有组件都在一个小于 100 MB 的二进制文件中。 为什么叫 K3s? 我们希望安装的 Kubernetes 只占用一半的内存。Kubernetes 是一个 10 个字母的单词&am…...