手写 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:实现常用的静态方法

    .catch
    class 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)的更多相关文章

  1. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  2. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  3. 【教程】手写简易web服务器

    package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...

  4. 手写简易SpringMVC

    手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...

  5. 手写简易的Mybatis

    手写简易的Mybatis 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一 ...

  6. Javascript之我也来手写一下Promise

    Promise太重要了,可以说是改变了JavaScript开发体验重要内容之一.而Promise也可以说是现代Javascript中极为重要的核心概念,所以理解Promise/A+规范,理解Promi ...

  7. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

  8. 手写简易WEB服务器

    今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...

  9. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

随机推荐

  1. 【LeetCode】851. Loud and Rich 解题报告(Python)

    [LeetCode]851. Loud and Rich 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http:// ...

  2. 【剑指Offer】删除链表中重复的结点 解题报告(Python)

    [剑指Offer]删除链表中重复的结点 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interview ...

  3. hdu-5587 Array(递归)

    Array Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Sub ...

  4. Joseph(hdu1443)

    Joseph Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  5. Docker 与 K8S学习笔记(五)—— 容器的操作(上篇)

    上一篇我们介绍了Dockerfile的基本编写方法,这一节我们来看看Docker容器的常用操作. 一.容器的运行方式 容器有两种运行方式,即daemon形式运行与非daemon形式运行,通俗地讲就是长 ...

  6. 用C++创建Https客户端,用Mingw编译

  7. 计算机视觉1->opencv4学习指南1 | 环境配置与例程

    opencv虽然很有名,但是自己一直没怎么玩过,暑假的时候使用深度相机做项目,但负责的不是代码模块,也只是配好了环境,没有继续了解图像处理.最近电子实习老师有教这个东西,但是身边不少同学遇到了麻烦,所 ...

  8. 【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 ...

  9. Not All Samples Are Created Equal: Deep Learning with Importance Sampling

    目录 概 主要内容 "代码" Katharopoulos A, Fleuret F. Not All Samples Are Created Equal: Deep Learnin ...

  10. 深入 Laravel 内核之IOC容器

    升级工厂前的准备工作 无规矩不成方圆,随着越来越多的行为出现,我们需要需要定下一些规范. 为了约束每一个行为的规范,需要定义一个行为接口: interface BehaviorInterface { ...