Promise是一种异步编程的解决方案,用写法看似链式的调用方式避免了异步回调过于复杂的情况,最近手写一个Promise以加深对其的理解并开拓一下思路。
- 准备工作
- 实现一个.then()
- 实现完整的Promise
准备工作
建立一个index.html和一个promise-extra.js,覆盖原有的Promise。最基本的,Promise有自己的状态,有then方法和catch方法,并且接收一个回调函数processor,其中可以执行异步或同步操作。
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写Promise</title>
</head>
<body>
<script src="./esimorp.js"></script>
<script>
console.log(Promise)
</script>
</body>
</html>
js代码
(function(global){
class Promise {
constructor(processor){
this._status = 'pending'
}
then(){}
catch(){}
}
global.Promise = Promise
})(window)
控制台输出
class Promise { index.html:11
constructor(processor){
this._status = 'pending'
}
then(){}
catch(){}
}
以上完成了就可以正式开始。。。
Promise中的回调函数processor应提供两个参数且这两个参数都是函数,以便用户对Promise当前的状态进行决议,当然,这个processor也不是必须的。另外Promise应该有将状态决议为fullfilled的方法和rejected的方法,我将其分别命名为_resolve和_reject,这两个函数接收的参数就是用户决议时传进来的参数。同时,then和catch都可以接收回调以便在Promise决议后进行相应的操作。于是把代码补充一下
class Promise {
constructor(processor){
this._status = 'pending'
const context = this
if(!processor){
return
}
processor(
res => {
context._resolve(res)
},
err => {
context._reject(err)
}
)
}
then(onFullfilled){
this.onFullfilled = onFullfilled
}
catch(onRejected){
this.onRejected = onRejected
}
_resolve(res){
this._status = 'fullfilled'
this.currentValue = res
}
_reject(err){
this._status = 'rejected'
this.currentErr = err
}
}
实现一个.then()
先实现一个.then(),这里要考虑两个情况:
- processor中的操作是异步的
- processor中的操作是同步的
......
then(onFullfilled){
this.onFullfilled = onFullfilled
//情况2同步执行,当状态为fullfilled时
if(this._status === 'fullfilled'){
this.onFullfilled(this.currentValue)
}
}
......
_resolve(res){
this._status = 'fullfilled'
this.currentValue = res
//情况1异步执行,onFullfilled先挂载,随后调用
if(this.onFullfilled){
this.onFullfilled(this.currentValue)
}
}
这样一个.then()就搞定了,.catch()同理。
实现完整Promise
那么要能够链式传递下去,.then()就不能只是执行一下回调onFullfilled,而是应该每次执行都返回一个新的Promise,我在每个Promise对象上添加了next属性,用于存放当前Promise对象的后一个新Promise对象。此外,把onFullfilled或onRejected函数的执行代码抽离出来,进行错误捕获,并对下一个Promise进行决议。还有一个情况要注意,例如当前Promise的状态决议为rejected了,但紧跟着的不是.catch()而是.then(),那么就要把这个状态传递下去,即对next进行_reject(),直到传到.catch()为止,反之同理。
......
/** onFullfilled和onRejected的统一执行方法
* @param value 当前传递的值 this.currentValue 或 this.currentErr
* @param processer 状态凝固之后的回调 this.onFullfilled 或 this.onRejected
* @param next 当前Promise的next
*/
taskCallback(value, processor, next){
let result = null
let normal = 1
try {
result = processor(value)
} catch (err){
normal = 0
result = err
}
//如果next的状态已凝固,那么其状态不可再变
if(next && (next._status ==='fullfilled' || next._status === 'rejected')){
return
}
//如果result仍然是Promise,那么执行其then或catch方法获得其返回值交由next决议
if(result instanceof Promise){
result.then(res => {
next._resolve(res)
})
result.catch(err => {
next._reject(err);
});
return
}
if(normal === 1){
next._resolve(result)
} else {
next._reject(result)
}
}
......
then(onFullfilled){
this.onFullfilled = onFullfilled
//创建一个新的Promise对象,挂到当前Promise对象的next属性上。不用传processor,因为.then()或.catch()产生的Promise不由用户手动决议
this.next = new Promise()
if(this._status === 'fullfilled'){
//执行onFullfilled的代码抽离成一个统一的函数
this.taskCallback(this.currentValue, this.onFullfilled.bind(this), this.next)
}
//当前状态为rejected时,对next进行_reject(),使状态传递下去
if(this._status === 'rejected'){
this.next._reject(this.currentErr)
}
return this.next //返回新的Promise
}
另外由于第一个手动声明的Promise只会挂载onRejected或onFullfilled中的其中一个(Promise后要么跟.then(onFullfilled)要么跟.catch(onRejected),故只能挂载一个),所以在原型方法中要初始化一下这两个方法,否则在_resolve()或_reject()时不执行taskCallback,也就不会开始链式调用。
......
onRejected(err){
this.next._reject(err)
}
onFullfilled(res){
this.next._resolve(res)
}
......
至此,已完成了Promise。参考then方法可以用同样的方法补全catch方法。
最后附上完整代码
(function(global){
class Promise {
constructor(processor){
this._status = 'pending'
const context = this
if(!processor){
return
}
processor(
res => {
context._resolve(res)
},
err => {
context._reject(err)
}
)
}
onRejected(err){
this.next._reject(err)
}
onFullfilled(res){
this.next._resolve(res)
}
taskCallback(value, processor, next){
let result = null
let normal = 1
try {
result = processor(value)
}
catch (err){
normal = 0
result = err
}
if(next && (next._status ==='fullfilled' || next._status === 'rejected')){
return
}
if(result instanceof Promise){
result.then(res => {
next._resolve(res)
})
result.catch(err => {
next._reject(err);
});
return
}
if(normal === 1){
next._resolve(result)
}
else {
next._reject(result)
}
}
then(onFullfilled){
this.onFullfilled = onFullfilled
this.next = new Promise()
if(this._status === 'fullfilled'){
this.taskCallback(this.currentValue, this.onFullfilled.bind(this), this.next)
}
if(this._status === 'rejected'){
this.next._reject(this.currentErr)
}
return this.next
}
catch(onRejected){
this.onRejected = onRejected
this.next = new Promise()
if(this._status === 'rejected'){
this.taskCallback(this.currentErr, this.onRejected.bind(this), this.next)
}
if(this._status === 'fullfilled'){
this.next._resolve(this.currentValue)
}
return this.next
}
_resolve(res){
this._status = 'fullfilled'
this.currentValue = res
if(this.next && this.onFullfilled){
this.taskCallback(this.currentValue, this.onFullfilled.bind(this), this.next)
}
}
_reject(err){
this._status = 'rejected'
this.currentErr = err
if(this.next && this.onRejected){
this.taskCallback(this.currentErr, this.onRejected.bind(this), this.next)
}
}
}
global.Promise = Promise
})(window)