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. Excel提取身份证年龄和性别③

    问题场景 从user表中的身份信息中拿到用户的年龄和性别: 以下方法也可适用于提取其他数据,目的在于通过实例操作了解更多函数用法: 以下图中数据都为测试数据,不具备真实性! 场景一 从user表中的1 ...

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

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

  3. Deep Learning-Based Video Coding: A Review and A Case Study

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! 1.Abstract: 本文主要介绍的是2015年以来关于深度图像/视频编码的代表性工作,主要可以分为两类:深度编码方案以及基于传统编码方 ...

  4. 简单实现C++Stack模板

    栈的特点是先进后出以及只能在栈顶进行插入和删除操作 本次实现的栈的基本操作: 1)弹栈 2)压栈 3)求栈大小 4)求栈容量 5)判断栈空 6)获取栈顶元素 1.用数组的方式实现栈基本操作 /** * ...

  5. Java多线程_并发容器ConcurrentHashMap/CopyOnWriteArrayList/CopyOnWriteArraySet

    ConcurrentHashMap         HashMap是线程不安全的,可以使用Collections.synchronizedMap(map)把一个不安全的map变成安全的,但是这里可以直 ...

  6. 区块链入门到实战(31)之Solidity – 第一个程序

    为简单起见,我们使用在线Solidity开发工具Remix IDE编译和运行Solidity程序. 第1步 – 在File explorers选项卡下,新建一个test1.sol文件,代码如下: 示例 ...

  7. day03 每日一行

    day03 每日一行 问题描述 用列表解释式 .生成器表达式实现 字典列表为: [{'first': 'john', 'last': 'smith', 'email': 'jsmith@exsampl ...

  8. vue-x和axios的学习

    axios的使用 使用原因:因为vue本身就带有处理dom的功能,不希望再有其他操作dom的插件,所以用axios代替了jquery 功能:发送xhr请求 下载: $ npm install axio ...

  9. Android开发工具资料Android Manifest 权限描述大全 随时随地查询权限描述。

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985转载请说明出处. 在文章最后面赠送了markdown格式的此文章内容下载.(不要再问问什么用markdo ...

  10. 沈阳做假证z

    沈阳做假证[电/薇:187ヘ1184ヘ0909同号]办各类证件-办毕业证-办离婚证,办学位证书,办硕士毕业证,办理文凭学历,办资格证,办房产证不. 这是一个简单的取最大值程序,可以用于处理 i32 数 ...