JavaScript之Promise实现原理(手写简易版本 MPromise)
手写 Promise 实现
Promise的基本使用
Promise定义及用法详情文档:Promise MAD文档
function testPromise(param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
params
? resolve('resolve:' + param)
: reject('reject:' + param)
}, 1000)
})
}
我们能够通过 .then 方法来获取执行成功或失败的结果,如:
const param = true
testPromise(param).then(res => {
// 当 param = true 时执行
console.log(res) // -> resolve: true
}, err => {
// 当 param = false 时执行
console.log(res) // -> reject: false
})
- Promise通常用于需要异步处理,比如HTTP请求等场景
- Promise会加入 JS 的微任务队列,故也可用于特定场景的优化处理
关于JS微任务队列文章参考:Vue3中微任务队列
实现Promise
- 目标1:实现简单版本的 MPromise 类
Promise是一个类,构造函数接受一个函数,这个函数的两个参数 resolve, reject 也是函数
Promise实际上是由三个状态来驱动的: PENDING(等待)、FULFILLED(完成)、REJECTED(拒绝)class MPromise{
// 分别设置Promise的三个执行状态
static PENDING = 'PENDING' // 等待
static FULFILLED = 'FULFILLED' // 已完成 .then
static REJECTED = 'REJECTED' // 已拒绝 .catch constructor(executor) {
// 初始化状态为 PENDING
this.status = MPromise.PENDING
// 分别存储执行 成功 和 执行 失败的值
this.resolveResult = undefined
this.rejectReason = undefined
// 存储回调函数
// 由于同一个 Promise 的.then函数可以调用多次,这里需要使用数组来存储
this.callback = []
// 将 执行函数 中的 resolve与 reject 方法执行 this 绑定
executor(this._resolve.bind(this), this._reject.bind(this))
} then(resolveFn, rejectFn) {
this.callback.push({
resolveFn,
rejectFn
})
} _resolve(result) {
// 更改状态
this.status = MPromise.FULFILLED
// 设置 .then 参数值
this.resolveResult = result
// 执行回调函数
this.callback.forEach(cb => this._handler(cb))
} _reject(errorBody) {
// 更改状态
this.status = MPromise.REJECTED
// 设置 .catch 参数值
this.rejectReason = errorBody
// 执行回调函数
this.callback.forEach(cb => this._handler(cb))
} // 根据当前的状态 执行对应的 callback 函数
_handler(callback) {
const { resolveFn, rejectFn } = callback if(this.status === MPromise.REJECTED && rejectFn) {
rejectFn(this.rejectReason)
} else if(this.status === MPromise.FULFILLED && resolveFn) {
resolveFn(this.resolveResult)
}
}
}
我们可以先对简单版本的 MPromise 进行测试
function testMPromise(test) {
return new MPromise((resolve, reject) => {
setTimeout(() => {
test ? resolve('resolve') : reject('resolve')
}, 500)
})
} const p = testMPromise(true).then(r => {
console.log('then: ', r) // -> 在 500ms 后输出: then:resolve
}) // 由于我们没有实现链式调用,p输出的是 undefined,所有 p.then 会抛出异常
console.log(p)
- 目标2:实现链式调用
实现链式调用我们需要在调用 then 方法时返回一个新的 MPromise 对象
然后我们需要对 _handle 函数进行改造,因为我们需要将上一次 then 函数的返回值传递下去
我们还需要处理 then 中返回的是一个 MPromise 对象的情况这里不能直接将 this 返回,我们必须保证每一个 MPromise 都是独立的,不然会造成内部变量的混乱
// 添加一个工具函数,判断是否为 MPromise 类型对象
const isMPromise = (obj) => obj instanceof MPromise class MPromise {
// ......
then(resolveFn, rejectFn) {
const newMPromiseCb = (nextResolveFn, nextRejectFn) => {
// 我们不能在使用 this.callback.push() 的方式添加回调函数
// 这样会导致不在同一个上下文(this)中
// 调用处理函数 _handler,在 _handler 函数中去添加 callback
// 这样就能保证往正确的上下文this中添加回调
this._handler({
resolveFn,
rejectFn,
nextResolveFn,
nextRejectFn,
})
} // 处理链式调用的问题:
// 创建一个 新的 MPromise 对象,并将其返回,使其能够进行链式调用
return new MPromise(newMPromiseCb)
} // 还需要考虑.then中返回的是一个 MPromise 对象该如何处理?
_resolve(result) {
if(isMPromise(result)) {
// 若上一次处理的返回值为一个 MPromise 对象
// 需要执行这个 MPromise
// 将 FULFILLED 和 REJECTED 状态分别交给 this._resolve 和 _reject去执行
result.then(
this._resolve.bind(this),
this._reject.bind(this)
)
} else {
// 更改状态
this.status = MPromise.FULFILLED
// 设置 .then 参数值
this.resolveResult = result
// 执行回调函数
this.callback.forEach(cb => this._handler(cb))
}
} _reject(errorBody) {
// 与 _resolve 中的处理逻辑相同
if(isMPromise(errorBody)) {
errorBody.then(
this._resolve.bind(this),
this._reject.bind(this)
)
} else {
// 更改状态
this.status = MPromise.REJECTED
// 设置 .catch 参数值
this.rejectReason = errorBody
// 执行回调函数
this.callback.forEach(cb => this._handler(cb))
}
} _handle(callback) {
const {
resolveFn,
rejectFn,
nextResolveFn
nextRejectFn
} = callback // 当 MPromise 状态为 PENDING 时将其回调函数收集到 this.callback 中
if(this.status === MPromise.PENDING) {
this.callback.push(callback)
return
} if(this.status === MPromise.REJECTED && rejectFn) {
const reason = rejectFn
? rejectFn(this.rejectReason)
:this.rejectReason
nextRejectFn(reason)
} else if(this.status === MPromise.FULFILLED && resolveFn) {
// 先判断是否传入了 resolveFn 回调函数
// 存在 则需要执行该函数,并将其返回值作为 nextResolveFn 的参数传入进去
const reason = rejectFn
? resolveFn(this.resolveResult)
: this.resolveResult
nextResolveFn(reason)
}
}
}
- 目标3:实现常用的静态方法
.catchclass MPromise{
// ...
// 添加 catch 函数
catch(rejectFn) {
// 我们只需要调用一下 then 方法,并将第一个参数传入 undefined 即可
return this.then(undefined, rejectFn)
}
// ...
}
.finally
class MPromise{
// ...
// 添加一个 finally 函数
finally(fn) {
// 这里我们只需要将传入的回调函数 fn,都当做 then 函数的参数传进去即可
// 因为 then 函数中会根据状态至少执行其中一个函数
this.then(fn, fn)
}
// ...
}
Promise.reject 与 Promise.resolve
class MPromise {
// ...
static reject(errorBody) {
// 其实只要注意判断传入的 参数 是否为一个 MPromise,或者是 存在 catch 属性的一个对象
if(errorBody instanceof MPromise || (typeof errorBody === 'object' && 'catch' in errorBody)) {
// 直接把这个对象返回就行了
return errorBody
} // 包装一下,返回一个状态为 REJECTED MPromise 对象即可
return new MPromise((resolve, reject) => {
reject(errorBody)
})
} // resolve 与 reject 方法一致
static resolve(resolveValue) {
if(errorBody instanceof MPromise || (typeof errorBody === 'object' && 'then' in resolveValue)) {
return resolveValue
}
return new MPromise(resolve => resolve(resolveValue))
}
// ...
}
Promise.all
class MPromise {
// ...
// iterables: 为数组类型
static all(iterables) {
const res = []
return new MPromise((resolve, reject) => {
iterables.forEach((c, index) => {
// MPromise.resolve 转换为 MPPromise 对象
MPromise.resolve(c).then(
(res) => {
// 收集结果
res.push(res) if(index >= iterables.length - 1) {
resolve(res)
}
},
// 若执行到 reject 则会直接停止调用并且返回当次执行失败的原因
reject
)
})
})
}
// ...
}
完整代码
1.手写Promise - 实现一个基础的Promise
2.手把手教你实现 Promise
3.简易版本没有对异常等逻辑进行处理
const isMPromise = (obj) => obj instanceof MPromise
class MPromise{
static PENDING = 'PENDING'
static FULFILLED = 'FULFILLED'
static REJECTED = 'REJECTED'
constructor(executor) {
// 初始化状态为 PENDING
this.status = MPromise.PENDING
// 分别存储执行 成功 和 执行 失败的值
this.resolveResult = undefined
this.rejectReason = undefined
// 存储回调函数
this.callback = []
// 将 执行函数 中的 resolve与 reject 方法执行 this 绑定
executor(this._resolve.bind(this), this._reject.bind(this))
}
then(resolveFn, rejectFn) {
// 实现链式调用,返回一个新的 MPromise 对象
const _promise = new MPromise((nextResolve, nextReject) => {
this._handler({
resolveFn,
rejectFn,
nextReject,
nextResolve
})
})
return _promise
}
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
// 添加一个 finally 函数
finally(fn) {
// 这里我们只需要将传入的回调函数 fn,都当做 then 函数的参数传进去即可
// 因为 then 函数中会根据状态至少执行其中一个函数
this.then(fn, fn)
}
// iterables: 为数组类型
static all(iterables) {
const res = []
return new MPromise((resolve, reject) => {
iterables.forEach((c, index) => {
// MPromise.resolve 转换为 MPPromise 对象
MPromise.resolve(c).then(
(res) => {
// 收集结果
res.push(res)
if(index >= iterables.length - 1) {
resolve(res)
}
},
// 若执行到 reject 则会直接停止调用并且返回当次执行失败的原因
reject
)
})
})
}
static reject(errorBody) {
if(errorBody instanceof MPromise || (typeof errorBody === 'object' && 'catch' in errorBody)) {
return errorBody
}
return new MPromise((resolve, reject) => {
reject(errorBody)
})
}
static resolve(resolveValue) {
if(errorBody instanceof MPromise || (typeof errorBody === 'object' && 'then' in resolveValue)) {
return resolveValue
}
return new MPromise(resolve => resolve(resolveValue))
}
_resolve(result) {
if(isMPromise(result)) {
result.then(
this._resolve.bind(this),
this._reject.bind(this)
)
} else {
this.status = MPromise.FULFILLED
this.resolveResult = result
// console.log('callback', this.callback)
this.callback.forEach(cb => this._handler(cb))
}
}
_reject(errorBody) {
if(isMPromise(errorBody)) {
result.then(
this._resolve.bind(this),
this._reject.bind(this)
)
} else {
this.status = MPromise.REJECTED
this.rejectReason = errorBody
this.callback.forEach(cb => this._handler(cb))
}
}
_handler(callback) {
if(this.status === MPromise.PENDING) {
this.callback.push(callback)
return
}
const {
resolveFn,
rejectFn,
nextReject,
nextResolve
} = callback
if(this.status === MPromise.REJECTED && rejectFn) {
const _reason = rejectFn ? rejectFn(this.rejectReason) : this.rejectReason
nextReject(_reason)
}else if(this.status === MPromise.FULFILLED && resolveFn) {
const _reason = resolveFn ? resolveFn(this.resolveResult) : this.resolveResult
// 将上一个 then 中的返回值作为下次 then 的参数传入
nextResolve(_reason)
}
}
}
--你我本是平凡人,平凡有多烦--
JavaScript之Promise实现原理(手写简易版本 MPromise)的更多相关文章
- JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(上)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...
- 【教程】手写简易web服务器
package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...
- 手写简易SpringMVC
手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...
- 手写简易的Mybatis
手写简易的Mybatis 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一 ...
- Javascript之我也来手写一下Promise
Promise太重要了,可以说是改变了JavaScript开发体验重要内容之一.而Promise也可以说是现代Javascript中极为重要的核心概念,所以理解Promise/A+规范,理解Promi ...
- Java多线程之Executor框架和手写简易的线程池
目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...
- 手写简易WEB服务器
今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...
- 手写简易版RPC框架基于Socket
什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...
随机推荐
- 【LeetCode】851. Loud and Rich 解题报告(Python)
[LeetCode]851. Loud and Rich 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http:// ...
- 【剑指Offer】删除链表中重复的结点 解题报告(Python)
[剑指Offer]删除链表中重复的结点 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interview ...
- hdu-5587 Array(递归)
Array Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Sub ...
- Joseph(hdu1443)
Joseph Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Sub ...
- Docker 与 K8S学习笔记(五)—— 容器的操作(上篇)
上一篇我们介绍了Dockerfile的基本编写方法,这一节我们来看看Docker容器的常用操作. 一.容器的运行方式 容器有两种运行方式,即daemon形式运行与非daemon形式运行,通俗地讲就是长 ...
- 用C++创建Https客户端,用Mingw编译
- 计算机视觉1->opencv4学习指南1 | 环境配置与例程
opencv虽然很有名,但是自己一直没怎么玩过,暑假的时候使用深度相机做项目,但负责的不是代码模块,也只是配好了环境,没有继续了解图像处理.最近电子实习老师有教这个东西,但是身边不少同学遇到了麻烦,所 ...
- 【Leetcode】718. 最长重复子数组
最长重复子数组有一下性质 A: [1,2,3,2,1] B: [3,2,1,4,7]设横是A竖是B,有规律:若横元和竖元相等,则为1,不等为0 1 2 3 2 13 0 0 1 0 12 0 1 0 ...
- Not All Samples Are Created Equal: Deep Learning with Importance Sampling
目录 概 主要内容 "代码" Katharopoulos A, Fleuret F. Not All Samples Are Created Equal: Deep Learnin ...
- 深入 Laravel 内核之IOC容器
升级工厂前的准备工作 无规矩不成方圆,随着越来越多的行为出现,我们需要需要定下一些规范. 为了约束每一个行为的规范,需要定义一个行为接口: interface BehaviorInterface { ...