Promise/A+规范以及实现
Promise 实现原理
Promise基本用法
new Promise(function(resolve, reject) {
resolve()
}).then(function(val) {
return val
}, function(error) {
catch(error)
}).catch(function(error) {
catch(error)
})
Promise对象基本方法是then
, 而catch
是then
的一个变形, 相当于then(undefined, onReject)
实现过程
根据Promise用法, 我们初步想到需要实现的方法是
- 构造函数
- resolve函数
- reject函数
- then函数
此时Promise原型应为
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECT = 'REJECT'
class Promise {
constructor(func) {}
resolve() {}
reject() {}
then(onReslove, onReject) {}
}
Promise/A+规范
(以下简称规范)中所说的
根据- Promise有三个状态
PENDING
,RESOLVED
,REJECTED
- 状态只会从
PENDING
转换到RESOLVED
或者REJECTED
其中一个, 并且之后不会再改变 - 当Promise处于执行态时, 会有一个终值, 并且该值不会再改变
- 当Promise处于拒绝态时, 会有一个据因, 并且该据因不会再改变
- 当Promise由PENDING转换为RESOLVED时, 会触发
onResolve
回调, 并且只执行一次 - 当Promise由PENDING转换为REJECTED时, 会触发
onReject
回调, 并且只执行一次 - Promise状态的转换时机在于开发者何时调用promise的resolve或者reject函数
class Promise {
constructor(func) {
this.value = null // 终值或者据因
this.status = PENDING // 状态
this.onResolveCallBack = [] // resolved 回调
this.onRejectCallBack = [] // rejected 回调
try {
func(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
this.reject(e)
}
}
resolve(val) {
if (this.status === PENDING) {
this.value = val // 设置终值
this.status = RESOLVED // 设置状态
this.onResolveCallBack.forEach(each => {
each(val) // 执行回调
})
}
}
reject(reason) {
if (this.status === PENDING) {
this.value = reason // 设置据因
this.status = REJECT // 设置状态
this.onRejectCallBack.forEach(each => {
each(reason) // 执行回调
})
}
}
then(onReslove, onReject) {}
}
这里可能有人会说Promise应该是一个异步的过程, 在上面代码中并没有看到任何的异步. 比如说: setTimeout。
解答:
其实当创建一个Promise实例的时候,整个过程是同步的。
也就是说
const ins = new Promise(function(res, rej) {
res(10)
})
console.log(ins)
console.log('after ins')
// 输出
// Promise {<resolved>: 10}
// after ins
当你执行完这一句, ins的状态会马上变成RESOLVED
. 说明在构造方法中并没有执行异步操作。如果真的需要异步的话,则需要主动在调用res
前,加上setTimeout
来触发异步。
const ins = new Promise(function(res, rej) {
setTimeout(() => {
res(10)
})
})
console.log(ins)
console.log('after ins')
// 输出
// Promise {<pending>}
// after ins
then
方法没有完成. 先看下规范怎么说
还有一个- 一个promise必须提供一个
then
方法以访问当前值, 终止和据因 - then接受两个参数
then(onResolve, onReject)
- onResolve和onReject都是可选, 如果不是函数则被忽略
- onResolve方法在promise执行结束后被调用, 其第一个参数为promise的终值, 被调用次数不超过一次
- onReject方法在promise被拒绝后被调用, 其第一个参数为promise的据因, 同样被调用次数不超过一次
- onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
- 如果onResolve和onReject返回一个值x, 则执行 Promise解决过程
- then方法必须返回一个
promise
对象
简单说就是
- 如果promise处于pending, 则将then回调放入promise的回调列表中
- 如果promise处于resolved, 则实行then方法中的onResolve
- 如果promise处于rejected, 则执行then方法中的onReject
- then方法要确保onResolve和onReject异步执行
- onResolve和onReject返回的值都将用来解决下一个promise(后面再讲解)
- 返回新的promise(注意: 一定是新的promise)
class Promise() {
// ...
then(onResolve, onReject){
const self = this
return new Promise(function(nextResolve, nextReject) {
if(self.status === PENDING) {
// 加入到任务队列
self.onResolveCallback.push(onResolve)
self.onRejectCallback.push(onReject)
} else if(self.status === RESOLVED) {
// 异步执行
setTimeout(onResolve, 0, self.value)
} else {
// 异步执行
setTimeout(onReject, 0, self.value)
}
})
}
}
此时Promise已经可以完成异步操作. 但是Promise还有一个关键特点是可以链式调用. 目前是还没有实现链式调用这一步. 具体代码看promise2.js
接下来继续看下规范怎么说
Promise 解决过程
- blablabla 这里比较长
简单说就是
x
为then
方法中onResolve
或者onReject
中返回的值, promise2
为then
方法返回的新promise
.
promise
的解决过程是一个抽象步骤. 需要输入一个promise
和一个值. 表示为[[Resolve]](promise, x)
- 如果
x
和promise2
相等, 则以TypeError
为据因拒绝执行promise2 - 如果
x
为Promise
实例, 则让promise2
接受x的状态 - 如果
x
为thenable
对象, 则调用其then
方法 - 如果都不满足, 则用
x
为参数执行promise2
继续修改then方法, 以及添加resolvePromise
来执行Promise
解决过程
function _isFunction(val) {
return typeof val === 'function'
}
function _isThenable(x) {
return _isFunction(x) || (typeof x === 'object' && x !== null)
}
/**
* Promise 解决过程
* 如果是thenable对象, 则触发该对象的then方法
* 如果是一个值, 则直接调用resolve解析这个值
* @param {Promise}} promise
* @param {Object} x
* @param {Function} resolve
* @param {Function} reject
*/
function resolvePromise(promise, x, resolve, reject) {
// 要求每次返回新的promise
// 如果返回是当前的promise, 则抛出typeError
if (x === promise) {
reject(new TypeError('Chaining cycle detected for promise'))
}
let called = false
// 判断是否thenable对象
if (_isThenable(x)) {
try {
const { then } = x
if (_isFunction(then)) {
then.call(
x,
val => {
if (!called) {
called = true
// 如果不断的返回thenable
// 则需要不断地递归
// 但是实际上不应该不断的返回thenable
resolvePromise(promise, val, resolve, reject)
}
},
reason => {
if (!called) {
called = true
reject(reason)
}
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) {
return
}
called = true
reject(e)
}
} else {
// 非thenable, 则以该值来执行resolve
resolve(x)
}
}
class Promise() {
// ...
/**
* then方法
* @param {Function} [onFulfilled] 前then的resolve函数, 当promise为RESOLVE时,处理当前结果
* @param {Function} onRejected 当前then的reject函数, 当promise被REJECT时调用
* @returns {Promise}
* @memberof Promise
*/
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected =
typeof onRejected === 'function'
? onRejected
: err => {
throw err
}
const self = this
// 如果有then方法调用, 则将hasThenHandle设为true
// console.log(this);
this.hasThenHandle = true
/**
* 返回一个新的promise, 用于链式调用
*/
const ret = new Promise(function(resolve, reject) {
// 用try..catch包裹执行方法
const tryCatchWrapper = function(fnc) {
return function() {
try {
fnc()
} catch (e) {
reject(e)
}
}
}
// 封装resolve方法回调
const doResolve = tryCatchWrapper(function() {
resolvePromise(ret, onFulfilled(self.value), resolve, reject)
})
// 封装reject方法回调
// 如果当前then没有相应的reject回调
const doReject = tryCatchWrapper(function() {
resolvePromise(ret, onRejected(self.value), resolve, reject)
})
if (self.status === PENDING) {
// 如果当前promise还未执行完毕, 则设置回调
self.onResolveCallback.push(doResolve)
self.onRejectCallback.push(doReject)
} else if (self.status === RESOLVED) {
// 如果为RESOLVE, 则异步执行resolve
setTimeout(doResolve, 0)
} else {
// 如果为REJECT, 则异步执行reject
setTimeout(doReject, 0)
}
})
return ret
}
}
至此一个Promise
可以说基本完成了.(完整代码请看index.js)
规范外的一些东西
其实规范中定义的是Promise
的构建和执行过程.
而我们日常用到的却不至于规范中所提到的.
比如
- catch
- finally
- Promise.resolve
- Promise.reject
- all (未实现)
- race (未实现)
那接下来就说下关于这部分的实现
catch
上面有提到. catch其实是then(undefined, reject)
的简写. 所以这里比较简单
class Promise() {
// ...
catch(reject) {
// 相当于新加入一个then方法
return this.then(undefined, reject)
}
}
finally (ES2018引入标准)
finally函数作用我想大家都应该知道, 就是无论当前promise状态是如何. 都一定会执行回调.
finally方法中, 不接收任何参数, 所以并不能知道前面的Promise的状态.
同时, 他不会对promise产生影响.总是返回原来的值 所以在finally
中的操作,应该是与状态无关, 不依赖于promise的执行结果
class Promise() {
// ...
finally(fnc = () => {}) {
return this.then(val => {
fnc()
return val
}, err => {
fnc()
throw err
})
}
}
Promise.resolve和Promise.reject (这里是从ES6入门中看到的定义)
// 调用形式
Promise.resolve(arg)
Promise.reject(arg)
Promise.resolve
根据arg的不同, 会执行不同的操作 - arg为Promise实例, 则原封不动的返回这个实例 - arg为thenable对象, 则会将arg转成promise, 并且立即执行
arg.then
方法(并不代表同步, 而是本轮事件循环结束时执行) - arg不满足上述情况, 则返回一个新的Promise实例, 状态为resolved, 终值为arg 因此Promise.resolve
是一个更方便的创建Promise
实例的方法.Promise.reject
这里就不会区分arg, 而是原封不动的把arg作为据因, 执行后续方法的调用.
实现代码
class Promise() {
// ...
/**
* Promise.resolve
* 将参数转成Promise对象
* @static
* @param {any} val
* @returns {MPromise}
* @memberof MPromise
*/
static resolve(x) {
// 如果为MPromise实例
// 则返回该实例
if(x instanceof Promise) {
return val
} else if(_isThenable(x)) {
// 如果为具有then方法的对象
// 则转为MPromise对象, 并且执行thenable
/**
* @example
* MPromise.resolve({
* then(res) {
* console.log('do promise')
* res(10)
* }
* })
*/
return new Promise(function(res, rej) {
// 执行异步
setTimeout(function() {
val.then(res, rej)
}, 0)
})
}
// 如果val为一个原始值,或者不具有then方法的对象
// 则返回一个新的MPromise对象,状态为resolved
/**
* @example
* MPromise.resolve()
*/
return new Promise(function(res) {res(x)})
}
/**
* reject方法参数会原封不动的作为据因而变成后续方法的参数
* 且初始状态为REJECT
* 不存在判别thenable
* @static
* @param {any} reason
* @returns
* @memberof MPromise
*/
static reject(reason) {
/**
* @example
* MPromise.reject('some error')
*/
return new Promise(function(res, rej) {rej(reason)})
}
}
开发过程中遇到其他问题
unhandledRejection
和浏览器中的Uncaught (in promise)
提示
node中的在Promise中产生的所有错误都会被Promise吞掉. 当没有相应的错误处理函数时候, node和浏览器分别有不同的表现.
但是这并不是一个新的错误, 因为不能用try{} catch(){}
捕获.
所以在浏览器端, 是一个console.error
的错误提示, 在node
中, 这个算是一个事件. 具体可以通过process.on
来监听
process.on('unhandledRejection', function(err, p) {
throw err
})
在编写代码中, 一开始卡在这一步挺久.
由于无法知道promise实例后续是否有相应的错误处理函数.
简单的判断onReject === undefined
是不行的.
形如:
Promise.reject(10)
// 或者
new Promise(function(res, rej) {
rej(10)
})
这类是同步执行的, onReject === undefined
恒为true
.
我的做法是给promise实例添加一个hasThenHandle
的属性, 在then
方法中将其设为true
在reject
方法中使用setTimeout
异步判断该值是否为true
, 如果不是则通过console.error
抛出提示.
其实在原生Promise中, 抛出的unhandledRejection
也是属于异步的.
Promise.reject(10)
console.log('after Promise.reject')
new Promise(function(res, rej) {
rej(10)
})
console.log('after new Promise')
// 输出
// after Promise.reject
// after new Promise
// Uncaught (in promise) 10
// Uncaught (in promise) 10
于是这个问题也能得到很好地解决.
至此完整代码已经结束, 具体看index.js
.
存在的问题
- 由于用的是setTimeout模拟, 所以优先级不能保证高于setTimeout
- 浏览器中可以用MessageChannel(macrotask)
- node中可以用setImmediate(优先级在某些情况下比setTimeout高一些)
- setTimeout和setImmediate在无IO操作下,两者执行顺序不确定,但是在IO操作下,setImmediate比setTimeout优先级高. 且setImmediate只在IE下有效