大家好,我是若川。最近组织了源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。

本文仓库 https://github.com/lxchuan12/delay-analysis.git,求个star^_^[1]

源码共读活动 每周一期,已进行到17期。于是搜寻各种值得我们学习,且代码行数不多的源码。delay 主文件仅70多行[2],非常值得我们学习。

阅读本文,你将学到:

1. 学会如何实现一个比较完善的 delay 函数
2. 学会使用 AbortController 实现取消功能
3. 学会面试常考 axios 取消功能实现
4. 等等

2. 环境准备

# 推荐克隆我的项目,保证与文章同步
git clone https://github.com/lxchuan12/delay-analysis.git
# npm i -g yarn
cd delay-analysis/delay && yarn i
# VSCode 直接打开当前项目
# code .
# 我写的例子都在 examples 这个文件夹中,可以启动服务本地查看调试
# 在 delay-analysis 目录下
npx http-server examples
# 打开 http://localhost:8080# 或者克隆官方项目
git clone https://github.com/sindresorhus/delay.git
# npm i -g yarn
cd delay && yarn i
# VSCode 直接打开当前项目
# code .

3. delay

我们从零开始来实现一个比较完善的 delay 函数[3]

3.1 第一版 简版延迟

要完成这样一个延迟函数。

3.1.1 使用

(async() => {await delay1(1000);console.log('输出这句');
})();

3.1.2 实现

PromisesetTimeout 结合实现,我们都很容易实现以下代码。

const delay1 = (ms) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve();}, ms);});
}

我们要传递结果。

3.2 第二版 传递 value 参数作为结果

3.2.1 使用

(async() => {const result = await delay2(1000, { value: '我是若川' });console.log('输出结果', result);
})();

我们也很容易实现如下代码。传递 value 最后作为结果返回。

3.2.2 实现

因此我们实现也很容易实现如下第二版。

const delay2 = (ms, { value } = {}) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(value);}, ms);});
}

这样写,Promise 永远是成功。我们也需要失败。这时我们定义个参数 willResolve 来定义。

3.3 第三版 willResolve 参数决定成功还是失败。

3.3.1 使用

(async() => {try{const result = await delay3(1000, { value: '我是若川', willResolve: false });console.log('永远不会输出这句');}catch(err){console.log('输出结果', err);}
})();

3.3.2 实现

加个 willResolve 参数决定成功还是失败。于是我们有了如下实现。

const delay3 = (ms, {value, willResolve} = {}) => {return new Promise((resolve, reject) => {setTimeout(() => {if(willResolve){resolve(value);}else{reject(value);}}, ms);});
}

3.4 第四版 一定时间范围内随机获得结果

延时器的毫秒数是写死的。我们希望能够在一定时间范围内随机获取到结果。

3.4.1 使用

(async() => {try{const result = await delay4.reject(1000, { value: '我是若川', willResolve: false });console.log('永远不会输出这句');}catch(err){console.log('输出结果', err);}const result2 = await delay4.range(10, 20000, { value: '我是若川,range' });console.log('输出结果', result2);
})();

3.4.2 实现

我们把成功 delay 和失败 reject 封装成一个函数,随机 range 单独封装成一个函数。

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);const createDelay = ({willResolve}) => (ms, {value} = {}) => {return new Promise((relove, reject) => {setTimeout(() => {if(willResolve){relove(value);}else{reject(value);}}, ms);});
}const createWithTimers = () => {const delay = createDelay({willResolve: true});delay.reject = createDelay({willResolve: false});delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);return delay;
}
const delay4 = createWithTimers();

实现到这里,相对比较完善了。但我们可能有需要提前结束。

3.5 第五版 提前清除

3.5.1 使用

(async () => {const delayedPromise = delay5(1000, {value: '我是若川'});setTimeout(() => {delayedPromise.clear();}, 300);// 300 milliseconds laterconsole.log(await delayedPromise);//=> '我是若川'
})();

3.5.2 实现

声明 settle变量,封装 settle 函数,在调用 delayPromise.clear 时清除定时器。于是我们可以得到如下第五版的代码。

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);const createDelay = ({willResolve}) => (ms, {value} = {}) => {let timeoutId;let settle;const delayPromise = new Promise((resolve, reject) => {settle = () => {if(willResolve){resolve(value);}else{reject(value);}}timeoutId = setTimeout(settle, ms);});delayPromise.clear = () => {clearTimeout(timeoutId);timeoutId = null;settle();};return delayPromise;
}const createWithTimers = () => {const delay = createDelay({willResolve: true});delay.reject = createDelay({willResolve: false});delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);return delay;
}
const delay5 = createWithTimers();

3.6 第六版 取消功能

我们查阅资料可以知道有 AbortController 可以实现取消功能。

caniuse AbortController[4]

npm abort-controller[5]

mdn AbortController[6]

fetch-abort[7]

fetch#aborting-requests[8]

yet-another-abortcontroller-polyfill[9]

3.6.1 使用

(async () => {const abortController = new AbortController();setTimeout(() => {abortController.abort();}, 500);try {await delay6(1000, {signal: abortController.signal});} catch (error) {// 500 milliseconds laterconsole.log(error.name)//=> 'AbortError'}
})();

3.6.2 实现

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);const createAbortError = () => {const error = new Error('Delay aborted');error.name = 'AbortError';return error;
};const createDelay = ({willResolve}) => (ms, {value, signal} = {}) => {if (signal && signal.aborted) {return Promise.reject(createAbortError());}let timeoutId;let settle;let rejectFn;const signalListener = () => {clearTimeout(timeoutId);rejectFn(createAbortError());}const cleanup = () => {if (signal) {signal.removeEventListener('abort', signalListener);}};const delayPromise = new Promise((resolve, reject) => {settle = () => {cleanup();if (willResolve) {resolve(value);} else {reject(value);}};rejectFn = reject;timeoutId = setTimeout(settle, ms);});if (signal) {signal.addEventListener('abort', signalListener, {once: true});}delayPromise.clear = () => {clearTimeout(timeoutId);timeoutId = null;settle();};return delayPromise;
}const createWithTimers = () => {const delay = createDelay({willResolve: true});delay.reject = createDelay({willResolve: false});delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);return delay;
}
const delay6 = createWithTimers();

3.7 第七版 自定义 clearTimeout 和 setTimeout 函数

3.7.1 使用

const customDelay = delay7.createWithTimers({clearTimeout, setTimeout});(async() => {const result = await customDelay(100, {value: '我是若川'});// Executed after 100 millisecondsconsole.log(result);//=> '我是若川'
})();

3.7.2 实现

传递 clearTimeout, setTimeout 两个参数替代上一版本的clearTimeout,setTimeout。于是有了第七版。这也就是delay的最终实现。

const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);const createAbortError = () => {const error = new Error('Delay aborted');error.name = 'AbortError';return error;
};const createDelay = ({clearTimeout: defaultClear, setTimeout: set, willResolve}) => (ms, {value, signal} = {}) => {if (signal && signal.aborted) {return Promise.reject(createAbortError());}let timeoutId;let settle;let rejectFn;const clear = defaultClear || clearTimeout;const signalListener = () => {clear(timeoutId);rejectFn(createAbortError());}const cleanup = () => {if (signal) {signal.removeEventListener('abort', signalListener);}};const delayPromise = new Promise((resolve, reject) => {settle = () => {cleanup();if (willResolve) {resolve(value);} else {reject(value);}};rejectFn = reject;timeoutId = (set || setTimeout)(settle, ms);});if (signal) {signal.addEventListener('abort', signalListener, {once: true});}delayPromise.clear = () => {clear(timeoutId);timeoutId = null;settle();};return delayPromise;
}const createWithTimers = clearAndSet => {const delay = createDelay({...clearAndSet, willResolve: true});delay.reject = createDelay({...clearAndSet, willResolve: false});delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);return delay;
}
const delay7 = createWithTimers();
delay7.createWithTimers = createWithTimers;

4. axios 取消请求

axios取消原理是:通过传递 config 配置 cancelToken 的形式,来取消的。判断有传cancelToken,在 promise 链式调用的 dispatchRequest 抛出错误,在 adapterrequest.abort() 取消请求,使 promise 走向 rejected,被用户捕获取消信息。

更多查看我的 axios 源码文章取消模块 学习 axios 源码整体架构,取消模块(可点击)

5. 总结

我们从零开始实现了一个带取消功能比较完善的延迟函数。也就是 delay 70多行源码[11]的实现。

包含支持随机时间结束、提前清除、取消、自定义 clearTimeout、setTimeout等功能。

取消使用了 mdn AbortController[12] ,由于兼容性不太好,社区也有了相应的 npm abort-controller[13] 实现 polyfill

yet-another-abortcontroller-polyfill[14]

建议克隆项目启动服务调试例子,印象会更加深刻。

# 推荐克隆我的项目,保证与文章同步
git clone https://github.com/lxchuan12/delay-analysis.git
cd delay-analysis
# 我写的例子都在 examples 这个文件夹中,可以启动服务本地查看调试
npx http-server examples
# 打开 http://localhost:8080

最后可以持续关注我@若川。欢迎加我微信 ruochuan12 交流,参与 源码共读 活动,每周大家一起学习200行左右的源码,共同进步。

参考资料

[1]

本文仓库 https://github.com/lxchuan12/delay-analysis.git,求个star^_^: https://github.com/lxchuan12/delay-analysis.git

[2]

delay 主文件仅70多行: https://github.com/sindresorhus/delay/blob/main/index.js

更多点击阅读原文查看。

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么相关推荐

  1. 面试官:手写一个call、apply、bind?

    前言 相信不少面试中涉及this的问题不少.而在JavaScript中this的绑定有四种类型,优先级分别为 new绑定>显式绑定>隐式绑定>默认绑定,这里我们也浅浅提及一下这四种的 ...

  2. 面试官:手写一个必然死锁的例子?一顿操作猛如虎。。

    来源:blog.csdn.net/xiewenfeng520/article/details/107230996 前言 只对死锁代码感兴趣的可以直接跳到第三小节 必然死锁示例,如果对死锁还不太了解的, ...

  3. 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】

    第二季:5值传递和引用传递[Java面试题] 前言 推荐 值传递 说明 题目 24 TransferValue醒脑小练习 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自 ...

  4. zookeeper springboot_摊牌了!我要手写一个“Spring Boot”

    ❝ 目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https:// ...

  5. 摊牌了!我要手写一个“Spring Boot”

    目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...

  6. Chat Top10 | 给面试官手写一个 Nacos,多少 K?

    每周推荐的最新 Chat Top10 没有固定主题,仅仅是编辑部参考多方评分和反馈挑选出来的好文章,不一定适合你的口味,建议小心食用- 我们一起看下第三期 Chat Top10 都有哪些内容 ???? ...

  7. ds查找—二叉树平衡因子_面试官让我手写一个平衡二叉树,我当时就笑了

    平衡二叉树对于初学者一直是一个比较复杂的知识点,因为其里面涉及到了大量的旋转操作.把大量的同学都给转晕了.这篇文章最主要的特点就是通过动画的形式演示.确保大家都能看懂.最后是手写一个平衡二叉树. 一. ...

  8. @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...

    大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...

  9. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

最新文章

  1. 我们从产品团队扩大中学到了什么
  2. windows kernel 可以直接读写文件系统资料吗_嵌入式杂谈之文件系统
  3. 模板打印:代码实现和总结
  4. 用心推荐三个嵌入式方向精品号
  5. DataTable的计算功能(转)
  6. [css] 重置(初始化)css的作用是什么?
  7. 笔记本鼠标乱跑!不知何故!
  8. keepalived高可用集群学习以及实验总结
  9. Java并发(四)——synchronized、volatile
  10. django-分页器
  11. Hierarchical voxel block hashing for effiecient integration of depth images
  12. mysql之为表添加一个字段并设定默认值
  13. oracle java 映射_java程序访问映射后的oracle
  14. Atitit 第三方登录与sso单点登录 单点登录:       我们的单点登录系统,主要包含了登录验证,token校验 、注销、注册几大功能,单点登录系统提供了统一的登录和注册页面,提供了统一的
  15. python import as 实例化_python中import list,dictionary常量在class实例化时遇到的坑
  16. 用傅里叶分析得到频域信息 MATLAB,信号分析实验_傅里叶matlab实现.doc
  17. SPSS独立样本t检验结果分析
  18. java之StringBuilder和关于数组怎么扩容
  19. 弱网优化、网络抖动、网络延时,这些问题,怎么处理?
  20. Rxjava:interval的使用

热门文章

  1. c语言用数组写密码程序,想程序高手求助--用C语言来编辑一个输入密码的程序...
  2. c语言结构体编程,[编程] C语言的结构体详解
  3. decimal double java_Java BigDecimal和double BigDecimal类
  4. git@github.com: Permission denied (publickey).
  5. PostgreSQL增强版命令行客户端(pgcli)
  6. 隐藏与禁用硬盘分区——利用工具或注册表
  7. 给linux用户加入sudo权限
  8. javascript编程风格(粗略笔记)
  9. video from html5
  10. 基于wemos D1的无线遥控灯(433m无线模块)