promise 标准

在实现 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 规范,所以我们在编写 Promise 时也应当遵循这个规范,建议认真、仔细读几遍这个规范。最好是理解事件循环,这样对于理解js中的异步是怎么回事非常重要。

https://promisesaplus.com/

基本使用

new Promise( function(resolve, reject) {...} /* executor */  );

new Promise((resolve, reject)=> {
AjaxRequest.post({
url: 'url',
data: {},
sueccess: ()=> {
resolve(res)
},
fail: (err)=> {
reject(err)
}
})
}).then((res)=> {
// do some
}).then(value => { }).catch((err)=> {
// do some
})

promise 是处理异步结果的一个对象,承若状态改变时调用对应的回调函数,resolve、reject用来改变promise 的状态,then 绑定成功、失败的回调。

环境准备

安装测试工具以及nodemon因为我们要在node环境调试自己写的promise

// nodemon
npm install nodemon -D
// promise 测试工具
npm install promises-aplus-tests -D

增加脚本命令

"testPromise": "promises-aplus-tests myPromise/promise3.js",
"dev": "nodemon ./myPromise/index.js -i "

各自的路径改成自己的即可,这个在后面会用来测试。

基本架子

根据规范实现一个简单的promise,功能如下

  1. promise的三种状态(PENDING、FULFILLED、REJECTED)
  2. 状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态
  3. 绑定then的回调
  4. 返回成功、失败的值
  5. 一个promise 支持调用多次then
  6. 支持捕获异常
/* 

基本架子
根据promise A+ 规范还要处理then链式调用以及返回值传递的问题,后续在promise2、promise3 处理 */ const PENDING = 'PENDING',
FULFILLED = 'FULFILLED',
REJECTED = 'REJECTED'; class myPromise {
constructor (executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined

this.onResolveCallbacks = []
this.onRejectedCallbacks = [] const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 发布
this.onResolveCallbacks.forEach(fn => fn())
}
} const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 发布
this.onRejectedCallbacks.forEach(fn => fn())
}
} try {
// 执行传进来的fn, 在给他提供改变状态的fn
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
// 订阅回调函数
then (onFulfilled, onRejected) { if (this.status = PENDING) {
// 订阅
this.onResolveCallbacks.push(() => {
onFulfilled(this.value)
}) this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
} if (this.status === FULFILLED) {
onFulfilled(this.value)
} if (this.status === REJECTED) {
onRejected(this.reason)
}
}
} module.exports = myPromise

订阅

传进来的fn是一个执行器,接受resolve、reject参数,通常我们在构造函数中需要调用某个接口,这是一个异步的操作,执行完构造函数之后,在执行then(),这个时候的状态还是pending,所以我们需要把then 绑定的回调存起来,也可以理解为promise对象订阅了这个回调。

发布

在 resolve,reject函数中中我们改变了promise 对象的状态,既然状态改变了,那么我们需要执行之前订阅的回调,所以在不同的状态下执行对应的回调即可。

流程

如上所示,实例化对象,执行构造函数,碰到异步,挂起,然后执行then()方法,绑定了resolve、reject的回调。如果异步有了结果执行对应的业务逻辑,调用resolve、或者reject,改变对应的状态,触发我们绑定的回调。

以上就是最基本的promise架子,但是还有promise 调用链没有处理,下面继续完善...

完善promise 调用链

promose 的精妙的地方就是这个调用链,首先then 函数会返回一个新的promise 对象,并且每一个promise 对象又有一个then 函数。惊不惊喜原理就是那么简单,回顾下then的一些特点

then 特点

  1. then 返回一个新的promise 对象
  2. then 绑定的回调函数在异步队列中执行(evnet loop 事件循环)
  3. 通过return 来传递结果,跟fn一样如果没有return,默认会是 underfined
  4. 抛出异常执行绑定的失败函数(最近的promise),如果没有,则执行catch
  5. then中不管是不是异步只要resolve、rejected 就会执行对应 onFulfilled、onRejected 函数
  6. then中返回promise状态跟执行回调的结果有关,如果没有异常则是FULFILLED,就算没有retun 也是FULFILLED,值是underfined,有异常就是REJECTED,接着走下个then 绑定的onFulfilled 、onRejected 函数

根据上面的特点以及阅读规范我们知道then()函数主要需要处理以下几点

  • 返回一个新的promise
  • 值怎么传给then返回的那个promise
  • 状态的改变

返回一个新的promise

因为promise 的链式调用涉及到状态,所以then 中返回的promise 是一个新的promise

then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
// do ...
})
return promise2
}

值的传递、状态的改变

let p = new myPromise((resolve, rejected) => {
// do ...
})
p.then(
value => {
return 1
},
reason => {}
)
.then(
value => {
return new Promise((resolve, rejected) => {
resolve('joel')
})
},
reason => {}
)
.then(
value => {
throw 'err: 出错啦'
},
reason => {}
)

then 返回的值可能是一个普通值、promise对象、function、error 等对于这部分规范文档也有详细的说明

[[Resolve]](promise, x)

这个可以理解为promise 处理的过程,其中x是执行回调的一个值,promise 是返回新的promise对象,完整代码如下

我们将这部分逻辑抽成一个独立的函数 如下

// 处理then返回结果的流程
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<myPromise>'))
} let called = false if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then
// 判断是否是promise
if (typeof then === 'function') {
then.call(x, (y) => {
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// 如果 x 不为对象或者函数,以 x 普通值执行回调
resolve(x)
}
}

测试

promises-aplus-tests 这个工具我们必须实现一个静态方法deferred,官方对这个方法的定义如下:

deferred: 返回一个包含{ promise, resolve, reject }的对象

promise 是一个处于pending状态的promise

resolve(value) 用value解决上面那个promise

reject(reason) 用reason拒绝上面那个promise

添加如下代码

myPromise.defer = myPromise.deferred = function () {
let deferred = {} deferred.promise = new myPromise((resolve, reject) => {
deferred.resolve = resolve
deferred.reject = reject
})
return deferred
}

在编辑执行我们前面加的命令即可

npm run testMyPromise

完善其他方法

  1. all
  2. allSettled
  3. any
  4. race
  5. catch
  6. finlly
npm run dev // 可以用来测试这些方法

源码

源码

参考

https://promisesaplus.com/

https://www.jianshu.com/p/4d266538f364

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

简单版 Promise/A+,通过官方872个测试用例的更多相关文章

  1. 手写一个Promise/A+,完美通过官方872个测试用例

    前段时间我用两篇文章深入讲解了异步的概念和Event Loop的底层原理,然后还讲了一种自己实现异步的发布订阅模式: setTimeout和setImmediate到底谁先执行,本文让你彻底理解Eve ...

  2. 手写简易版Promise

    实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了. ...

  3. echarts怎么使用(最最最最简单版)(本质canvas)

    echarts怎么使用(最最最最简单版)(本质canvas) 一.总结 一句话总结:外部扩展插件肯定要写js啊,不然数据怎么进去,不然宽高怎么设置.本质都是canvas嵌套在页面上,比如div中. 1 ...

  4. 全栈前端入门必看 koa2+mysql+vue+vant 构建简单版移动端博客

    koa2+mysql+vue+vant 构建简单版移动端博客 具体内容展示 开始正文 github地址 <br/> 觉得对你有帮助的话,可以star一下^_^必须安装:<br/> ...

  5. JavaMail简单版实验测试

    前言: 最近由于实现web商城的自动发送邮件功能的需求,故涉猎的邮箱协议的内部原理.现将简单版的Java Mail实例做个代码展示,并附上其中可能出现的bug贴出,方便感兴趣的读者进行测试! 1.载入 ...

  6. 小米抢购(简单版v0.1)-登录并验证抢购权限,以及获取真实抢购地址

    小米(简单版)-登录并验证抢购权限,以及获取真实抢购地址! 并不是复制到浏览器就行了的   还得传递所需要的参数 这里只是前部分  后面的自己发挥了 { "stime": 1389 ...

  7. 一个简单的Promise 实现

    用了这么长时间的promise,也看了很多关于promise 的文章博客,对promise 算是些了解.但是要更深的理解promise,最好的办法还是自己实现一个. 我大概清楚promise 是对异步 ...

  8. Java实现简单版SVM

    Java实现简单版SVM 近期的图像分类工作要用到latent svm,为了更加深入了解svm,自己动手实现一个简单版的.         之所以说是简单版,由于没实用到拉格朗日,对偶,核函数等等.而 ...

  9. Microsoft Visual Studio 2012旗舰版(VS2012中文版下载)官方中文版

    Microsoft Visual Studio 2012 Ultimate旗舰版(VS2012中文版下载)是一个最先进的开发解决方案,它使各种规模的团队能够设计和创建出使用户欣喜的引人注目的应用程序. ...

随机推荐

  1. 算法-图(2)Bellman-Ford算法求最短路径

    template <class T,class E> void Bellman-Ford(Graph<T,E>&G, int v, E dist[], int path ...

  2. CentOS 6.x/7.x上安装git

    yum安装 # yum info git # yum install -y git 可以通过下面的命令来检查是否安装了git环境 git --version 参考:如何在CentOS 6.x/7.x上 ...

  3. 使用 Swift Package Manager 集成依赖库

      本文首发于 Ficow Shen's Blog,原文地址: 使用 Swift Package Manager 集成依赖库.   内容概览 前言 添加依赖包 在项目中使用依赖 管理已导入的依赖 在团 ...

  4. [SCOI2013]摩托车交易 题解

    思路分析 为了让交易额尽量大,显然我们需要尽量多地买入.对于每个城市,到达这个城市时携带的黄金受到几个条件的影响:之前卖出的黄金,之前能买入的最多的黄金,前一个城市到当前城市的路径上的最小边权.既然不 ...

  5. 华为SEO搜索引擎主管招聘内容

    http://www.wocaoseo.com/thread-166-1-1.html 华为SEO搜索引擎主管招聘内容: 职位职责 1. 提出全站的SEO策略和实施计划,推动和监督计划实施:负责提升各 ...

  6. python_选择排序

    #选择排序 def insert_sort(li): for i in range (1,len(li)): # i表示摸到牌的下标 tem = li[i] j = i - 1 # j 是初始手中的牌 ...

  7. Clock()函数简单使用(C库函数)

    Clock()函数简单使用(转) 存在于标准库<time.h> 描述 C 库函数 clock_t clock(void) 返回程序执行起(一般为程序的开头),处理器时钟所使用的时间.为了获 ...

  8. 4300 字Python列表使用总结,用心!

    今天列表专题的目录如下: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这 ...

  9. Spine学习六 - 碰撞检测

    相信在使用Spine做游戏的时候,肯定会遇到这样的需求: 一个人物有一把大刀,要使用这把大刀去砍怪,伤害检测以这把大刀砍刀怪物为准,那么要怎么在一个看上去就是一体的Spine Object上绑定一个碰 ...

  10. 基于Celery在多台云服务器上实现分布式

    起源 最近参加公司里的一个比赛,比赛内容里有一项是尽量使用分布式实现项目.因为项目最终会跑在jetsonnano,一个贼卡的开发板,性能及其垃圾.而且要求使用python? 找了很多博客,讲的真的是模 ...