Promise是ES6的核心内容,有效解决回调地狱的问题,我们在此解读Promise/A+规范,并手写实现一个Promsie。
文章内容主要来源于实现一个完美符合Promise/A+规范的Promise
个人博客主要作为笔记记录使用。如不允许转载,烦请联系删除。感谢。
解读一下Promise/A+规范
术语
要求
一个promise必须有3个状态,pending,fulfilled(resolved),rejected当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。
promise英文译为承诺,也就是说promise的状态一旦发生改变,就永远是不可逆的。
一个promise必须有一个then方法,then方法接受两个参数:
1 2
| promise.then(onFulfilled,onRejected)
|
- 为了实现链式调用,then方法必须返回一个promise
1
| promise2=promise1.then(onFulfilled,onRejected)
|
初始版本的Promise
一个简单版本的Promise,注意其尚不能处理异步任务,.then也不能返回Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| function myPromise(constructor){ let self = this self.status = "pending" self.value = undefined self.reason = undefined function resolve(value){ if(self.status === "pending"){ self.value = value self.status = "fullFilled" } } function reject(reason){ if(self.status === "pending"){ self.reason = reason self.status = "rejected" } }
try{ constructor(resolve, reject) }catch(e){ reject(e) } }
myPromise.prototype.then = function(onFullfilled, onRejected){ let self = this switch(self.status){ case "fullFilled": onFullfilled(self.value) break; case "rejected": onRejected(self.reason) break; default: break; } }
|
这里myPromise还无法处理异步的resolve
比如:
1 2 3 4 5 6 7
| var p=new myPromise(function(resolve,reject){ setTimeout(function(){ resolve(1) },1000) }); p.then(function(x){console.log(x)})
|
基于观察者模式处理异步
用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。
基本逻辑就是:
- 在.then执行的时候,如果状态还是pending,那么就把回调函数收集到数组中
- 在执行resolve或者reject的时候,执行数组中保存的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| function myPromise(constructor){ let self = this self.status = "pending" self.value = undefined self.reason = undefined self.onFullFilledArray = [] self.onRejectedArray = []
function resolve(value){ if(self.status === "pending"){ self.value = value self.status = "fullFilled" self.onFullFilledArray.forEach( fn => { fn(self.value) }) } } function reject(reason){ if(self.status === "pending"){ self.reason = reason self.status = "rejected" self.onRejectedArray.forEach(fn => { fn(self.reason) }) } }
try{ constructor(resolve, reject) }catch(e){ reject(e) } }
myPromise.prototype.then = function(onFullFilled, onRejected){ let self = this switch(self.status){ case "pending": self.onFullFilledArray.push(function(value){ onFullFilled(value) }) self.onRejectedArray.push(function(reason){ onRejected(reason) }) break; case "fullFilled": onFullFilled(self.value) break; case "rejected": onRejected(self.reason) break; default: break; } }
|
经过这样改造,再看上边的异步任务的例子:
- 开始new Promise中执行到了定时器会将其加入到宏任务队列,并不会立即执行定时器中的内容
- 紧接着执行.then方法,此时由于定时器中的内容还未执行,所以status为pending,我们将onFullFilled和onRejected方法收集起来
- 执行完之后会执行定时器也就会调用resolve方法,此时改变status的状态,并通知收集的依赖进行更新执行相应的回调。
异步的问题解决了,但是Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。
then方法实现链式调用
要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分
由于只需要对then方法进行修改,下边就不展示构造函数的内容了,只放then方法上来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| myPromise.prototype.then = function(onFullFilled, onRejected){ let self = this let promise2; switch(self.status){ case "pending": promise2 = new myPromise(function(resolve, reject){ self.onFullFilledArray.push(function(value){ try{ let temp = onFullFilled(value) resolve(temp) }catch(e){ reject(e) } }) self.onRejectedArray.push(function(reason){ try{ let temp = onRejected(reason) resolve(temp) }catch(e){ reject(e) } }) }) break; case "fullFilled": promise2 = new myPromise(function(resolve, reject){ try{ let temp = onFullFilled(self.value) resolve(temp) }catch(e){ reject(e) } }) break; case "rejected": promise2 = new myPromise(function(resolve, reject){ try{ let temp = onRejected(self.reason) resolve(temp) }catch(e){ reject(e) } }) break; default: break; } return promise2 }
|
由于掺杂了异步的逻辑可能有些乱,可以只看状态为fullFilled和rejected的情况会清晰一些,然后加上异步的处理就可以理解了。
基本流程就是我们会在.then方法中创建一个新的promsie返回来实现.then的链式调用。
在这个新的promise中,我们要执行之前的promise.then的回调函数,并根据其回调函数的返回值在新的回调函数中执行resolve/reject操作,以保证在下一个.then方法中可以接收到我们传递的值。
还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。
解决then函数中的onFullfilled和onRejected方法的返回值问题
特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。
首先来看promise中对于onFullfilled函数的返回值的要求
如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误
如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise
如果返回的是一个嵌套类型的promsie,那么需要递归。
如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。
根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| function myPromise(constructor){ let self = this self.status = "pending" self.value = undefined self.reason = undefined self.onFullFilledArray = [] self.onRejectedArray = []
function resolve(value){ if(self.status === "pending"){ self.value = value self.status = "fullFilled" self.onFullFilledArray.forEach( fn => { fn(self.value) }) } } function reject(reason){ if(self.status === "pending"){ self.reason = reason self.status = "rejected" self.onRejectedArray.forEach(fn => { fn(self.reason) }) } }
try{ constructor(resolve, reject) }catch(e){ reject(e) } }
myPromise.prototype.then = function(onFullFilled, onRejected){ let self = this let promise2; switch(self.status){ case "pending": promise2 = new myPromise(function(resolve, reject){ self.onFullFilledArray.push(function(value){ setTimeout(function(){ try{ let temp = onFullFilled(value) resolvePromise(promise2, temp, resolve, reject) }catch(e){ reject(e) } }) }) self.onRejectedArray.push(function(reason){ setTimeout(function(){ try{ let temp = onRejected(reason) resolvePromise(promise2 ,temp, resolve, reject) }catch(e){ reject(e) } }) }) }) break; case "fullFilled": promise2 = new myPromise(function(resolve, reject){ setTimeout(function(){ try{ let temp = onFullFilled(self.value) resolvePromise(promise2 ,temp, resolve, reject) }catch(e){ reject(e) } }) }) break; case "rejected": promise2 = new myPromise(function(resolve, reject){ setTimeout(function(){ try{ let temp = onRejected(self.reason) resolvePromise(promise2 ,temp, resolve, reject) }catch(e){ reject(e) } }) }) break; default: break; } return promise2 }
function resolvePromise(promise, x, resolve,reject){ if(promise === x){ throw new Error("type error") } let isUsed; if(x!==null && (typeof x==="object" || typeof x === "function")){ try{ let then = x.then if(typeof then === "function"){ then.call(x, function(y){ if(isUsed){ return; } isUsed = true resolvePromise(promise, y, resolve, reject) }, function(e){ if(isUsed){ return; } isUsed = true reject(e) }) }else{ resolve(x) } }catch(e){ if(isUsed){ return; } isUsed = true reject(e) } }else{ resolve(x) } }
|