面试官:能不能给 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 就代表着要进行状态转换。不过 取消 这两个字相信一定不会陌生,clearTimeoutclearInterval 嘛。

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 来表示取消的期限,而这个期限到底要设置多少才合适呢...

至于说进度通知功能,仁者见仁智者见智吧...

但不管怎么样两个功能实现的思路都是比较有趣的,而且不太常见,不考虑实用性确实能够成为一道考题,只能说很符合面试官的口味😏

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

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

相关文章

常用的图像大小进行操作

一&#xff1a;在图像读取时&#xff0c;常用的读取方式包括PIL和CV2。 1&#xff1a;用PIL读取 首先载入PIL&#xff0c;接着用Image打开图片。 from PIL import Image image Image.open("D:/PycharmProjects/python function/0005.png") print(type(image)) imag…

每日一练:LeeCode-501、二叉搜索树中的众数【二叉搜索树+pre辅助节点+DFS】

本文是力扣LeeCode-LeeCode-501、二叉搜索树中的众数【二叉搜索树pre辅助节点DFS】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;…

个性签名大全

只许一生浮世清欢愿我以孤独作为铠甲&#xff0c;自此不再受伤愿我是阳光&#xff0c;明媚而不忧伤我不敢太勇敢太执着太骄傲&#xff0c;我怕失去开始你是我的天使&#xff0c;最后你是我的唯一姐的霸气&#xff0c;无人能比&#xff0c;哥的傲气&#xff0c;无人能朋唯有万事…

Unity面试手册:初中级面试题

1.请简述ArrayList和List的主要区别&#xff1f; ArrayList存在不安全类型&#xff08;ArrayList会把所有插入其中的数据都当做Object来处理&#xff09;&#xff0c;装箱拆箱的操作&#xff08;费时&#xff09;&#xff0c;List是泛型类&#xff0c;功能跟ArrayList相似&…

ASUS华硕枪神8笔记本电脑G614JIR,G814JVR,G634JYR,G834JZR工厂模式出厂Windows11系统 带重置还原功能

适用ROG枪神8系列笔记本型号&#xff1a; G614JIR、G614JVR、G634JYR、G634JZR G814JIR、G814JVR、G834JYR、G834JZR 链接&#xff1a;https://pan.baidu.com/s/1tYZt6XFNC2d6YmwTbtFN7A?pwd3kp8 提取码&#xff1a;3kp8 带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主…

说一下 JVM 有哪些垃圾回收器?如何选择垃圾收集器?notify()和notifyAll()有什么区别?

说一下 JVM 有哪些垃圾回收器&#xff1f; 如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。下图展示了 7 种作用于不同分代的收集器&#xff0c;其中用于回收新生代的收集器包括 Serial 、 PraNew 、 Parallel Scavenge &#xf…

JSONObject.parseObject的使用-嵌套json

pom文件 <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.54</version></dependency>具体代码 import com.alibaba.fastjson.JSONObject;public class JsonLearn {public static …

PyCharm - Run Debug 程序安全执行步骤

PyCharm - Run & Debug 程序安全执行步骤 1. Run2. DebugReferences 1. Run right click -> Run ‘simulation_data_gene…’ or Ctrl Shift F10 2. Debug right click -> Debug ‘simulation_data_gene…’ 在一个 PyCharm 工程下&#xff0c;存在多个 Pytho…

3个密码学相关的问题

一、离散对数问题&#xff08;Discrete Logarithm Problem, DLP&#xff09; 问题描述&#xff1a;给定 有限阿贝尓群 G中的2个元素a和b&#xff0c;找出最小的正整数x满足&#xff1a;b a ^^ x &#xff08;或者证明这样的x不存在&#xff09;。 二、阶数问题&#xff08;O…

Python实现水雨情遥测终端串口通信

Python实现水雨情遥测终端的串口通信&#xff0c;可以使用pySerial库。pySerial是一个Python串行端口通信库&#xff0c;它可以让你简单地与串行设备进行通信。以下是一个基本的示例&#xff0c;说明如何使用pySerial库进行串口通信&#xff1a; 首先&#xff0c;你需要安装py…

【PyQt】13-对话框

文章目录 前言一、知识储备二、详细展开2.1 通用对话框-QDialog2.1 消息类型对话框-QMessageBox运行结果 2.2 输入对话框 QInputDilog运行结果 2.3 字体对话框-QFontDialog运行结果 2.4 颜色对话框运行结果 2.5 文件对话框运行结果 总结 前言 1、四种形式的对话框。 2、警告框…

深入理解 Vue3 中的 setup 函数

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

搞不明白这4类车,别去自驾游

文 | AUTO芯球 作者 | 太2 春天要来了 又可以去浪了 喜欢自驾的朋友憋不住了吧&#xff0c; 鄙人不才全国各地走过&#xff0c;各种车型也大致开过。 那么根据我这几年的自驾经历 来跟大家聊一聊我觉得比较方便好用的自驾车。 第一类&#xff0c;城市SUV 宝马X&#xff…

vue框架-vue-cli

vue-cli Vue CLI是一个官方的脚手架工具,用于快速搭建基于Vue.js的项目。Vue CLI提供了一整套可配置的脚手架,可以帮助开发人员快速构建现代化的Web应用程序。 Vue CLI通过提供预先配置好的Webpack模板和插件,使得开发人员可以在不需要手动编写Webpack配置的情况下快速创建…

【Docker】docker安装

需要云服务器等云产品来学习Linux可以移步/-->腾讯云<--/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;新用户首次下单享超低折扣。 目录 一、Ubuntu安装docker 1、查看操作系统和CPU架构 2、卸载docker 3、配置docker下载源 4、安装docker 5、解决报错…

【STM32 物联网】AT指令与TCP,发送与接收数据

文章目录 前言一、连接TCP服务器1.1 配置Wifi模式1.2 连接路由器1.3 查询ESP8266设备IP地址1.4 连接TCP服务器 二、向服务器接收数据和发送数据2.1 发送数据2.2 接收数据 总结 前言 随着物联网&#xff08;IoT&#xff09;技术的迅速发展&#xff0c;越来越多的设备和系统开始…

【开源】C++ 周期任务调度的思想和实现

​ 今天调休&#xff0c;抓住年假的最后一天&#xff0c;将构思多日适合将并行任务串行执行的框架实现出来。 核心思想&#xff1a; 将各个独立的功能模块作为周期性的任务。在主循环集中调度所有任务&#xff0c;让各个功能模块依次有处理事项的机会。如果处理事项较为耗时&…

BES 平台 SDK之串口指令

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; 上位机下发格式&#xff1a; 格式一&#xff1a;[A,B] 格式二&#xff1a;A:B 固定格式&#xff1a;auto_test&#xff1a…

政安晨:【完全零基础】认知人工智能(二)【超级简单】的【机器学习神经网络】—— 底层算法

如果小伙伴第一次看到这篇文章&#xff0c;可以先浏览一下我这个系列的上一篇文章&#xff1a; 政安晨&#xff1a;【完全零基础】认知人工智能&#xff08;一&#xff09;【超级简单】的【机器学习神经网络】 —— 预测机https://blog.csdn.net/snowdenkeke/article/details/…

阿里云服务器CPU内存配置怎么选择够用?

阿里云服务器配置怎么选择&#xff1f;根据实际使用场景选择&#xff0c;个人搭建网站可选2核2G配置&#xff0c;访问量大的话可以选择2核4G配置&#xff0c;企业部署Java、Python等开发环境可以选择2核8G配置&#xff0c;企业数据库、Web应用或APP可以选择4核8G配置或4核16G配…