- A+
注: 本文中写的类只是为了了解Promise
类的内部原理而模拟出来一个, 并不一定符合类似的规范或者效率多么高, 但是基本的功能还是实现了的.
注: 本文代码运行环境: NodeJS v14.9.0
用法
如下, 这是一个传统的使用回调函数的异步代码
function getAnInt(callback) { setTimeout(() => { callback(81) }, 500) } function sqrt(n, resolve, reject) { setTimeout(() => { let res = Math.sqrt(n) if (parseInt(res) === res) { resolve(res) } else { reject("cannot get an int") } }, 500) } let errHandler = err => console.log("Error " + err) getAnInt(v1 => { console.log(v1) sqrt(v1, v2 => { console.log(v2) sqrt(v2, v3 => { console.log(v3) sqrt(v3, v4 => { console.log(v4) }, errHandler) }, errHandler) }, errHandler) })
执行结果:
81 9 3 Error cannot get an int
有没有感觉眼花缭乱? 这金字塔状的代码被亲切地称为回调地狱, 下面就是我们的主角Promise
上场的时候了, 酱酱酱酱
function getAnInt() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(81) }, 500) }) } function sqrt(n) { return new Promise((resolve, reject) => { setTimeout(() => { let res = Math.sqrt(n) if (parseInt(res) === res) { resolve(res) } else { reject("cannot get an int") } }, 500) }) } getAnInt().then(v1 => { console.log(v1) return sqrt(v1) }).then(v2 => { console.log(v2) return sqrt(v2) }).then(v3 => { console.log(v3) return sqrt(v3) }).then(v4 => { console.log(v4) }).catch(err => { console.log("Error " + err) })
执行结果:
81 9 3 Error cannot get an int
结果一模一样, 但是这个代码写出来的感觉, 就是要清晰了好多好多好多好多好多好多好多好多好多好多好多好多
介绍
在Promise/A+标准中定义了Promise
到底是个什么东西, 这里挑出重点部分, 其余的规范如果想看的话点这里去官网
promise
含有then
方法, 没有规定其它的方法.then
方法会返回一个新的promise
then
方法的参数是onFulfilled, onRejected
, 它们都是可选的(当然都是函数类型)promise
有三个状态,pending(代办)
,fulfilled(完成)
和rejected(被拒绝)
, 状态只能从pending
转成另外两个, 然后就不能再转了.- 如果
onRejected
或者onFulfilled
返回了一个Promise
对象, 需要得出它的结果再传给下一个then
方法里对应的地方
因为本文代码中有很多的 resolve
, 所以这里的代码使用resolved(被解决)
代替fulfilled
为什么没有列出来更多的内容呢, 因为其它的内容大多和兼容性有关, 与这个实现原理关系不是太大, 还有的是到具体实现函数的时候才会用到的规范, 所以我没有列出来
注: catch
方法是ES6标准里的, 它的原理是then(null, onRejected)
实现
注: 本文代码不考虑throw
, 为了只体现原理, 让代码尽可能更简单.
构造函数
Promise
的构造函数通常传入一个执行者函数, 这个函数里面可能是异步逻辑(这么说的意思就是也可能不是), 接受两个参数: resolve
和reject
.
-
调用
resolve(value)
就代表方法成功执行了,Promise
会把resolve
中传入的value
传给then
方法里的参数 -
调用
reject(reason)
就是执行出错了,Promise
会把reject
中传入的reason
传给then
方法里的参数
好, 下面开始做点准备工作
const Pending = 'pending' const Resolved = 'resolved' const Rejected = 'rejected' class MyPromise {}
诶, 这段代码我感觉不用解释了吧? 下面的我会在注释或者是代码块下方说明
class MyPromise { constructor(executor) { // 状态 this.status = Pending // 正常运行返回的结果 this.value = null // 发生错误的原因 this.reason = null // 详见这段代码块下面写的 注1 this.onRejected = () => {} this.onResolved = () => {} let resolve = value => { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Resolved this.value = value this.onResolved(value) } let reject = reason => { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Rejected this.reason = reason this.onRejected(reason) } // 见 注2 executor(resolve, reject) } }
-
注1: 这是两个被
reject
或者resolve
后调用的回调函数, 我看的别人实现的版本大多是一个数组, 然后调用的时候一个接一个调用里面的函数.我认为对同一个promise调用多次
then
方法的时候很少, 而且本文只是一个思路展示, 并不严格遵守A+规范, 所以这里就直接写了个什么也没干的函数在这里也分析一下, 在
then
方法调用的时候, 如果调用then
时的状态是Pending
, 那么就设置一下当前对象里的onRejected
和onResolved
, 具体设置什么在后面的代码里会提到; 如果状态不是Pending
, 就代表这两个函数早就执行完了, 就需要根据this.value
和this.reason
具体的调用then
函数中传进来的onRejected
和onResolved
. -
注2: 这里直接同步调用了, 没有异步调用. 因为如果这个操作真的需要异步的话, 在
executor
函数里面就会有异步方法了(如setTimeout
), 不需要Promise
类给它办.
Then方法
然后就是then
方法啦~
注意: then
方法要求每次返回新的Promise
对象.
先写个框架
then(onResolved, onRejected) { let funcOrNull = f => typeof f === "function" ? f : null onResolved = funcOrNull(onResolved) onRejected = funcOrNull(onRejected) if (this.status === Rejected) { return new MyPromise((resolve, reject) => { }) } else if (this.status === Resolved) { return new MyPromise((resolve, reject) => { }) } else { return new MyPromise((resolve, reject) => { }) } }
Rejected
如果是状态是rejected, 那么
if (this.status === Rejected) { return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } }) }
这些实现的代码包括下面的elseif和else块就是最难理解的了, 我当时是好久好久也没有理解, 接下来我会就像数学里面一样分类讨论:
关于Rejected块的详细说明(尽管也就10行)
理解了Rejected块, 那么Resolved块和他几乎一模一样, 只是函数名字不一样而已, 所以我这里会分析的尽可能详细
-
如果调用的时候是这样的:
new MyPromise((resolve, reject) => { reject("I rejected the promise") }).then(null, console.log)
先分析构造方法, 创建
Promise
对象的时候, 这里它的状态就变成Rejected
, 但是其他的什么事都没干, 让我们来看前面的代码this.onRejected = () => {} this.onResolved = () => {} let reject = reason => { if (this.status !== Pending) { return } this.status = Rejected this.reason = reason this.onRejected(reason) } executor(resolve, reject)
这个时候
this.onRejected
还是个空函数, 所以调用它也没什么用
接下来到then
方法了, 让我们来看上面if块里的代码return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } })
可以看出它执行了
let value = onRejected(reason)
, 然后调用resolve(value)
, 之后这个新的Promise
状态就是Resolved
了.至于为什么这里要用
resolve
, 我是通过NodeJS做了个实验看看NodeJS对这件事是怎么干的, 代码如下let p1 = new Promise((resolve, reject) => { reject("I rejected the promise") }) let p2 = p1.then(null, reason => { return 'I am from onRejected function' }) // 这里是为了不管到底是什么状态都能把p1和p2输出出来 p2.then(() => console.log(p1, p2), () => console.log(p1, p2))
输出(原本的执行结果没有换行, 我为了方便看自己加上的)
Promise { <rejected> 'I rejected the promise' } Promise { 'I am from onRejected function' }
这就看出来NodeJS是在处理完错误之后把
onRejected
的返回值用resolve
函数处理了
-
如果调用的时候是这样的
new MyPromise((resolve, reject) => { reject("I just rejected the promise") }).then(null, null).then(null, console.log)
这个时候就要考虑不能把错误信息丢掉了, 为了实现这个"穿透"功能, 我们可以研究一下NodeJS是怎么干的
let p1 = new Promise((resolve, reject) => { reject("I rejected the promise") }).then(null, null) p1.then(() => console.log(p1), () => console.log(p1))
输出
Promise { <rejected> 'I rejected the promise' }
这就很简单了, NodeJS是把新的
Promise
对象继续调用reject
并且传递错误信息. 所以再看上面if块里的代码return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } })
可以看出这里也是在
onRejected
空的时候直接用reject
方法把新的Promise
对象的状态设置成了Rejected
并且也把this.reason
错误信息传了过去.
展现成代码的话, 就是执行了reject(this.reason)
你可能会疑惑, 那么
reject(this.reason)
返回值应该是undefined
, 然后又调用了resolve(value)
是怎么回事呢?
这里我们要看前面的代码let resolve = value => { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Resolved this.value = value this.onResolved(value) }
在调用完
reject
之后, 这里的status
就变成了Rejected
, 这个方法就不会调用了呀
你可能还会疑惑, 这里的代码
if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) }
虽然说你知道返回值是
Promise
要得出结果, 但这是onRejected
返回的值, 为什么第二行要这么写?还是老方法, 我们看看NodeJS这个地方怎么实现的
let p = new Promise((resolve, reject) => { reject("I rejected the promise") }).then(null, reason => { return new Promise((resolve, reject) => { resolve("Hello~") }) }).then(value => { console.log("Value " + value) }, reason => { console.log("Reason " + reason) })
运行结果
Value Hello~
所以说, 这里需要这么写, 让这个
then
里返回的Promise
对象then
方法的onResolved
方法直接调用新对象的resolve
和reject
方法来操作这个新对象
Resolved
如果上面的都能理解了, 那么下面这个elseif
块就特别好理解了
else if (this.status === Resolved) { return new MyPromise((resolve, reject) => { let value = (onResolved === null ? resolve : onResolved)(this.value) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } } }
Pending
在else
块里, 也就是状态是Pending
的时候, 需要做的事情几乎和上面的if
和elseif
块一样
在Promise
对象状态是Pending
的时候, 不能通过this.value
和this.reason
获取值, 但是, 我们可以通过设置this.onRejected
和this.onResolved
这两个函数, 因为当Promise
的executor
执行完的时候一定会调用这两个函数中的一个, 并且调用它们的时候都会带上value
和reason
, 所以这里的代码需要这么写
else { return new MyPromise((resolve, reject) => { this.onResolved = value => { let v = (onResolved === null ? resolve : onResolved)(value) if (v instanceof MyPromise) { v.then(resolve, reject) } else { resolve(v) } } this.onRejected = reason => { let v = (onRejected === null ? reject : onRejected)(reason) if (v instanceof MyPromise) { v.then(resolve, reject) } else { resolve(v) } } }) }
最后加上一个catch
方法, 其实就是一个语法糖, 既然ES6都加上了, 那我也加上吧
catch(onRejected) { return this.then(null, onRejected) }
最后的测试
嘿咻, 终于弄完了, 接下来就是实验新对象的时候啦!(这么说好像有点怪怪的呢)
还是文章开头那熟悉的味道
function getAnInt() { return new MyPromise((resolve, reject) => { setTimeout(() => { resolve(81) }, 500) }) } function sqrt(n) { return new MyPromise((resolve, reject) => { setTimeout(() => { let res = Math.sqrt(n) if (parseInt(res) === res) { resolve(res) } else { reject("cannot get an int") } }, 500) }) } getAnInt().then(v1 => { console.log(v1) return sqrt(v1) }).then(v2 => { console.log(v2) return sqrt(v2) }).then(v3 => { console.log(v3) return sqrt(v3) }).then(v4 => { console.log(v4) }).catch(err => { console.log("Error " + err) })
结果
81 9 3 Error cannot get an int
附: 全代码
const Pending = 'pending' const Resolved = 'resolved' const Rejected = 'rejected' class MyPromise { constructor(executor) { // 状态 this.status = Pending // 正常运行返回的结果 this.value = null // 发生错误的原因 this.reason = null // 见 注1 this.onRejected = () => {} this.onResolved = () => {} let resolve = value => { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Resolved this.value = value this.onResolved(value) } let reject = reason => { // 如果不是Pending就忽略 if (this.status !== Pending) { return } this.status = Rejected this.reason = reason this.onRejected(reason) } // 见 注2 executor(resolve, reject) } then(onResolved, onRejected) { let funcOrNull = f => typeof f === "function" ? f : null onResolved = funcOrNull(onResolved) onRejected = funcOrNull(onRejected) if (this.status === Rejected) { return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } }) } else if (this.status === Resolved) { return new MyPromise((resolve, reject) => { let value = (onResolved === null ? resolve : onResolved)(this.value) if (value instanceof MyPromise) { value.then(resolve, reject) } else { resolve(value) } }) } else { return new MyPromise((resolve, reject) => { this.onResolved = value => { let v = (onResolved === null ? resolve : onResolved)(value) if (v instanceof MyPromise) { v.then(resolve, reject) } else { resolve(v) } } this.onRejected = reason => { let v = (onRejected === null ? reject : onRejected)(reason) if (v instanceof MyPromise) { v.then(resolve, reject) } else { resolve(v) } } }) } } catch(onRejected) { return this.then(null, onRejected) } } // 测试模块! function getAnInt() { return new MyPromise((resolve, reject) => { setTimeout(() => { resolve(81) }, 500) }) } function sqrt(n) { return new MyPromise((resolve, reject) => { setTimeout(() => { let res = Math.sqrt(n) if (parseInt(res) === res) { resolve(res) } else { reject("cannot get an int") } }, 500) }) } getAnInt().then(v1 => { console.log(v1) return sqrt(v1) }).then(v2 => { console.log(v2) return sqrt(v2) }).then(v3 => { console.log(v3) return sqrt(v3) }).then(v4 => { console.log(v4) }).catch(err => { console.log("Error " + err) })
参考: https://zhuanlan.zhihu.com/p/21834559
https://zhuanlan.zhihu.com/p/183801144