[手写系列] 带你实现一个简单的Promise
简介
学习之前 需要先对Promise有个基本了解哦,这里都默认大家都是比较熟悉Promise的
本次将带小伙伴们实现Promise的基本功能
Promise的基本骨架Promise的thenPromise.then的多次调用then链式调用catch的实现finally的实现
01-搭建基本骨架
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
console.log(value);
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
console.log(reason);
}
}
executor(resolve, rejected)
}
}
// 初步搭建好Promise的construtor结构
const promise = new ZXPromise((resolve, rejected) => {
resolve("123");
rejected("wushichu")
})
- 因为
Promise有三种状态pending,fulfilled,rejected,我们这里就声明三个常量来代表这三种状态 Promise中需要传递一个回调函数,他的参数中包含了resolve和rejected,调用resolve之后,状态会变为fulfilled,调用rejected,状态会变成rejected- 我定义了一个类,我们在
constructor中定义所需要的resolve和rejected函数,然后将这两个函数传入那个executor中去,这样Promise的基本骨架就已经搭建完成了,非常简单.
02-实现Promise的then功能
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
//因为只有pending状态才能进行变化
if(this.status!==PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED;
if (this.onfufilled)
this.onfufilled(value);
})
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if(this.status!==PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED;
if (this.onrejected)
this.onrejected(reason);
})
}
}
executor(resolve, rejected)
}
then(onfufilled, onrejected) {
this.onfufilled = onfufilled;
this.onrejected = onrejected;
}
}
// 接下来开始写then方法
const promise = new ZXPromise((resolve, rejected) => {
resolve("123");
rejected("wushichu");
})
promise.then((res) => {
console.log("res", res);
}, (err) => {
console.log("err", err);
})
then方法中接受两个参数,分别是onfulfilled和onrejected两个函数,分别对应着状态fulfilled和rejected- 这里要注意一个点我在
resolve和rejected中都使用了queueMicrotask,这里使用的目的是为了保证顺序执行的一致性,确保在then方法执行过后,再去执行相关代码,这里需要大家熟悉微任务队列和宏任务队列,推荐大家看下这篇文章
03-Promise.then多次调用
大家可以用上一部分的代码实验一下,如果多次调用,会发现只有最后一个输出,并且在定时器中使用,会出现结果为undefined
p1.then((res) => {
console.log("res1", res);
});
p1.then((res) => {
console.log('res2: ', res);
});
setTimeout(() => {
p1.then((res) => {
console.log("res4", res);
})
}, 1000);
现在我们来解决下上述问题,看代码
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onfufilled = [];
this.onrejected = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onfufilled.forEach(fn => {
fn(value);
});
})
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onrejected.forEach(fn => {
fn(reason);
})
})
}
}
executor(resolve, rejected)
}
// 接下来为了Promise能够多次调用 进行优化
then(onfufilled, onrejected) {
if (this.status === PROMISE_STATUS_FULFILLED) {
onfufilled(this.value);
}
if (this.status === PROMISE_STATUS_REJECTED) {
onrejected(this.value);
}
if (this.status === PROMISE_STATUS_PENDING) {
this.onfufilled.push(onfufilled);
this.onrejected.push(onrejected);
}
}
}
- 因为改进之后,需要存储
resolve和rejected的value和reason值,所以我们定义了这两个值 - 为了满足多次调用,我们需要将
promise中的onfulfilled和onrejected改为数组存储以用来满足我们的多次调用 - 定时器的问题我这边说下,因为
setTimeout属于宏任务,在同步代码执行完毕之后,会接着执行微任务,所以宏任务是最后来执行的,所以也就造成了promise中的代码执行完了,但是包裹在定时器中的then方法没有获取到结果 - 所以呢,在这里我决定让处于定时器中的代码直接执行而不压入数组中去,因为定时器之前的代码已经执行完毕了,
promise的状态也已经发生了改变,所以我就在then方法中判断promise的状态,如果是fulfilled和rejected状态的话,传过来的函数就直接执行
04-then方法的链式调用
要想实现链式调用,那么then方法肯定是将Promise对象又给返回出来了,说到这了大家有没有思路呢?
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onfufilled = [];
this.onrejected = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onfufilled.forEach(fn => {
fn(value);
});
})
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onrejected.forEach(fn => {
fn(reason);
})
})
}
}
try{
executor(resolve, rejected)
}catch(err){
console.log(err);
}
}
then(onfufilled, onrejected) {
return new ZXPromise((resolve, rejected) => {
if (this.status === PROMISE_STATUS_FULFILLED) {
try {
//如果then中有返回值,就会作为下一个then所接收的值
const value = onfufilled(this.value);
resolve(value);
} catch (err) {
rejected(err);
}
}
if (this.status === PROMISE_STATUS_REJECTED) {
try {
const value = onrejected(this.value);
resolve(value);
} catch (err) {
rejected(err);
}
}
if (this.status === PROMISE_STATUS_PENDING) {
try {
this.onfufilled.push(() => {
const value = onfufilled(this.value);
resolve(value);
});
} catch (err) {
rejected(err);
}
try {
this.onrejected.push(() => {
const value = onrejected(this.value);
resolve(value);
});
} catch (err) {
rejected(err);
}
}
})
}
}
const promise = new ZXPromise((resolve, rejected) => {
resolve("123");
rejected("wushichu");
})
promise.then((res) => {
console.log("res1:", res);
return "abc";
}, (err) => {
console.log("err1", err);
}).then((res) => {
console.log("res2", res);
}, (err) => {
console.log("err2", err);
})
- 变化最大的就是then方法了,大家可以看到我又把
ZXPromise返回出去了,代码中我写的很清楚了
05-catch方法实现
catch方法实际上是then第二个参数的语法糖,说到这里大家有没有明白什么呢?
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
const execFnWithCatchError = (execFn, value, resolve, reject) => {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onfufilled = [];
this.onrejected = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onfufilled.forEach(fn => {
fn(value);
});
})
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onrejected.forEach(fn => {
fn(reason);
})
return this.reason;
})
}
}
executor(resolve, rejected)
}
then(onfufilled, onrejected) {
//这一段是为了将错误代码传递下去的
const defaultOnRejected = err => { throw err }
onrejected = onrejected || defaultOnRejected
return new ZXPromise((resolve, rejected) => {
if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
execFnWithCatchError(onfufilled, this.value, resolve, rejected);
}
if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
execFnWithCatchError(onrejected, this.reason, resolve, rejected);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onfufilled)
this.onfufilled.push(() => {
execFnWithCatchError(onfufilled, this.value, resolve, rejected);
});
if (onrejected) {
this.onrejected.push(() => {
execFnWithCatchError(onrejected, this.reason, resolve, rejected);
});
}
}
})
}
catch(onrejected) {
return this.then(undefined, onrejected);
}
}
- 大家可以看到
catch代码实际上就只有一行,就是将then方法进行了调用,是不是相当简单呢 - 然后我觉得那个
try catch代码重复性比较高,所以我将它提取了出来复用 - 然后大家看下那个
then里面的开头,onrejected函数被给予了一个默认值,如果then没有传递第二个参数,那么会被赋予一个错误处理函数的默认值,抛出错误后,会自动被try catch捕获进行reject,这样子错误会被层层传递,一直到最后被catch函数所执行.
06-finally的实现
finally就是要在最后执行的函数,无论什么情况,实现起来也是非常简单
finally(fn) {
return this.then(() => { fn() }, () => { fn() });
}
- 在类中加上这一段代码就好了,因为finally是无法接收任何resolve和rejected的值的,所以我们在传递的函数中执行
fn,就是避免resolve的值和rejected的值被传递到finally上去
07-完整代码总览
const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";
const execFnWithCatchError = (execFn, value, resolve, reject) => {
try {
const result = execFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class ZXPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onfufilled = [];
this.onrejected = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
this.onfufilled.forEach(fn => {
fn(value);
});
})
}
}
const rejected = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
this.onrejected.forEach(fn => {
fn(reason);
})
return this.reason;
})
}
}
executor(resolve, rejected)
}
then(onfufilled, onrejected) {
//这一段是为了将错误代码传递下去的
const defaultOnRejected = err => { throw err }
onrejected = onrejected || defaultOnRejected
return new ZXPromise((resolve, rejected) => {
if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
execFnWithCatchError(onfufilled, this.value, resolve, rejected);
}
if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
execFnWithCatchError(onrejected, this.reason, resolve, rejected);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onfufilled)
this.onfufilled.push(() => {
execFnWithCatchError(onfufilled, this.value, resolve, rejected);
});
if (onrejected) {
this.onrejected.push(() => {
execFnWithCatchError(onrejected, this.reason, resolve, rejected);
});
}
}
})
}
catch(onrejected) {
return this.then(undefined, onrejected);
}
finally(fn) {
return this.then(() => { fn() }, () => { fn() });
}
}
- 大家可以自行进行测试
[手写系列] 带你实现一个简单的Promise的更多相关文章
- 手写系列:call、apply、bind、函数柯里化
少废话,show my code call 原理都在注释里了 // 不覆盖原生call方法,起个别名叫myCall,接收this上下文context和参数params Function.prototy ...
- 一个简单的Promise 实现
用了这么长时间的promise,也看了很多关于promise 的文章博客,对promise 算是些了解.但是要更深的理解promise,最好的办法还是自己实现一个. 我大概清楚promise 是对异步 ...
- [手写系列] Spirit带你实现防抖函数和节流函数
前言 防抖函数和节流函数,无论是写业务的时候还是面试的时候,想必大家已经听过很多次了吧.但是大家在用到的时候,有了解过他们之间的区别嘛,他们是如何实现的呢?还是说只是简单的调用下像lodash和und ...
- 手写系列-实现一个铂金段位的 React
一.前言 本文基于 https://pomb.us/build-your-own-react/ 实现简单版 React. 本文学习思路来自 卡颂-b站-React源码,你在第几层. 模拟的版本为 Re ...
- 自己动手系列----使用数组实现一个简单的Map
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同.Java 语言中提供的数组是用来存储固定大小的同类型元素. 这里提一下,数组的优缺点: 优点: 1. 使用索 ...
- Tomcat详解系列(1) - 如何设计一个简单的web容器
Tomcat - 如何设计一个简单的web容器 在学习Tomcat前,很多人先入为主的对它的认知是巨复杂的:所以第一步,在学习它之前,要打破这种观念,我们通过学习如何设计一个最基本的web容器来看它需 ...
- Go组件学习——手写连接池并没有那么简单
1.背景 前段时间在看gorm,发现gorm是复用database/sql的连接池. 于是翻了下database/sql的数据库连接池的代码实现,看完代码,好像也不是很复杂,但是总觉得理解不够深刻,于 ...
- 【 D3.js 入门系列 --- 3 】 做一个简单的图表!
前面说了几节,都是对文字进行处理,这一节中将用 D3.js 做一个简单的柱形图. 做柱形图有很多种方法,比如用 HTML 的 div 标签,或用 svg . 推荐用 SVG 来做各种图形.SVG 意为 ...
- JBoss 系列七十:一个简单的 CDI Web 应用
概述 本文通过一个简单的 CDI Web 应用演示dependency injection, scope, qualifiers 以及EL整合.应用部署完成后我们可以通过http://localhos ...
随机推荐
- 人口信息普查系统-JavaWeb-二
上次发表了人口普查系统的题目要求,今天和大家分享一下我的技术方案. 技术上用到的是html+jsp+JavaBean+servlet+JavaScript 其实现在的前端页面主流还是html,它可以实 ...
- 【Containerd版】Kubeadm高可用安装K8s集群1.23+
目录 基本环境配置 节点规划 网段规划及软件版本 基本配置 内核升级配置 K8s组件及Runtime安装 Containerd安装 K8s组件安装 高可用实现 集群初始化 Master01初始化 添加 ...
- python 小兵 三元运算符
1 if 条件成立: 2 val = 1 3 else: 4 val = 2 改成三元运算: val = 1 if 条件成立 else 2 举例 条件成立走左边,条件成立走右边 a = 2 b = 5 ...
- react之withRouter的作用
withRouter的作用:把不是通过路由切换过来的组件,将react-router的history.location和match三个对象传入到props对象上: 默认情况下必须是经过路由匹配渲染的组 ...
- PyTorch 介绍 | AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD
训练神经网络时,最常用的算法就是反向传播.在该算法中,参数(模型权重)会根据损失函数关于对应参数的梯度进行调整. 为了计算这些梯度,PyTorch内置了名为 torch.autograd 的微分引擎. ...
- JspSmartUpload 简略中文API文档
感谢原文作者:~数字人生~ 原文链接:https://www.cnblogs.com/mycodelife/archive/2009/04/26/1444132.html 一.JspSmartUplo ...
- Function.prototype.bind、call与apply
学习Function.prototype.bind.call与apply时,看到一篇博客,学到一些内容,但由于博客时间太久,根据官方文档对内容进行一些修正:下文为修正过内容 前言 前段时间面试遇见一题 ...
- 位运算符、|和||、&和&&的区别
一.位运算符操作的都是整数类型 1.<<:左移,在一定范围内向左移动n位,相当于乘以2的n次幂 左移不管是正数还是负数,都是在后面添0: 2.>>:右移,在一定范围内向右移动n ...
- 关于String不可变的一些理解
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11683720.html 一直对String不可变感到疑问, 不知为何说String是不可变的, ...
- 微服务如何聚合 API 文档?这波秀~
今天这篇文章介绍一下微服务如何聚合Swagger实现接口文档管理. 文章目录如下: 为什么需要聚合? 微服务模块众多,如果不聚合文档,则访问每个服务的API文档都需要单独访问一个Swagger UI界 ...