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. Golang bytes.buffer详解

    原文:https://www.jianshu.com/p/e53083132a25 Buffer 介绍 Buffer 是 bytes 包中的一个 type Buffer struct{…} A buf ...

  2. 个人项目作业WC(JAVA)

    GitHub地址:https://github.com/1666403186/WC 一.题目描述 Word Count1. 实现一个简单而完整的软件工具(源程序特征统计程序).2. 进行单元测试.回归 ...

  3. postman 基本应用

    前言 进行post高级应用的一个整理. 正文 批量测试和简单自动化测试 在点击collects的列表中,会弹出下面这个选项. 上面有3个按钮,分别是分享.运行.展示在网页中. 那么就看下这个运行吧. ...

  4. Java多线程_CAS算法和ABA问题

    CAS算法概述CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换.CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B. CAS指令执行时,当且仅当内存地址V ...

  5. 阿里大牛教你基于Python的 Selenium自动化测试示例解析

    今天给大家讲解的是自动化测试示例的解析,如有不对的地方请多多指教. 自动化测试示例如下: from selenium import webdriver from selenium.webdriver. ...

  6. 在Spring中拦截器的使用

    Filter Filter是Servlet容器实现的,并不是由Spring 实现的 下面是一个例子 import java.io.IOException; import javax.servlet.F ...

  7. 【MarkDown】github readme添加图片 Markdown语法添加图片,适用各种markdown语法

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, QQ986945193 微博:http://weibo.com/mcxiaobing 首先给大家看一 ...

  8. <string name="xxx"> 的复杂用法:格式化及使用html标签

    1.官方文档: https://developer.android.com/guide/topics/resources/string-resource 2.格式化字符串 2.1 示例 <res ...

  9. tokitsukaze and RPG(暴力优化)

    链接:https://ac.nowcoder.com/acm/contest/308/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  10. My Github Repository

    最近在Github上整了个Repository来保存打过的比赛的代码,包括Codeforces,Google Code Jam和Google Kick Start等,之后应该也会搞一点刷题的代码. 之 ...