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有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...
随机推荐
- Codeforces 567C:Geometric Progression(DP)
time limit per test : 1 second memory limit per test : 256 megabytes input : standard input output : ...
- Java 泛型通配符 T,E,K,V,?
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被 ...
- [炼丹术]使用Pytorch搭建模型的步骤及教程
使用Pytorch搭建模型的步骤及教程 我们知道,模型有一个特定的生命周期,了解这个为数据集建模和理解 PyTorch API 提供了指导方向.我们可以根据生命周期的每一个步骤进行设计和优化,同时更加 ...
- Python猫 2021 文章小结,翻译竟比原创多!
最近给自己放了两周的"长假",刷视频.看小说.玩游戏,就是不写文章不更新公众号. 半途而废的事情令得 2021 年的时间流逝加快,最后留下只是遗憾和不甘. 又到了新的一年,按照惯例 ...
- 最小生成树Kruskal算法(1)
概念 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. [1] 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆) ...
- xftp 6 的 使用
1.前言 xftp是个向云服务器linux系统传输文件的软件,装载在window系统 简单易用 2.下载 官方下载地址:https://www.netsarang.com/zh/xftp-downlo ...
- 【Azure 应用服务】一个 App Service 同时部署运行两个及多个 Java 应用程序(Jar包)
问题描述 如何在一个AppService下同时部署运行多个Java 应用程序呢? 问题解答 因为App Service的默认根目录为 wwwroot.如果需要运行多个Java 应用程序,需要在 www ...
- 曼孚科技:“四管齐下”筑牢AI数据隐私安全防线
谈及数据,绕不开的一个话题就是数据隐私与数据安全.随着数字化进程加快,数据安全事件频发,据Risk Based Security统计,去年国际数据泄露事件近5000起,被泄露数据近41亿条,数据造成的 ...
- Google Java 风格指南(Google Java Style Guide)
官方地址 google.github.io 本文档作为 Google 的 Java 编程语言源代码编码标准的完整定义.当且仅当它遵守此处的规则时,Java 源文件才被描述为 Google 风格. 前言 ...
- [Windows]为windows系统鼠标右键添加软件和图标
转载自 https://blog.csdn.net/p312011150/article/details/81207059 一.打开注册表 首先打开windows的注册表,当然了,我个人倾向于 (1) ...