說到 ES6,Promise 是繞不過的問題;如果說 ES6 的 Class 是基于 Javascript 原型繼承的封裝,那麼 Promise 則是對 callback 回調機制的改進。這篇文章,不談 Promise 的實際應用;聊一下 Promise 的實作原理,從最簡單的解決方案入手,一步一步的自己實作一個 SimplePromise。
正文
從最簡單的 Promise 初始化和使用入手:
const pro=new Promise ((res, rej)=> {})
pro.then(data=> {}, err=> {})
Promise 的構造函數如上,需要傳遞一個函數作為參數,這個函數有兩個變量: resolve, reject。而 Promise 有不同的執行狀态,分三種情況:Resolve, Reject, Pending。根據以上的資訊,寫出最基本的 SimplePromise 的類結構:
class SimplePromise{
constructor(handler){
this._status="PENDING"
handler(this._resolve.bind(this), this._reject.bind(this))//參數函數的作用域指向Class
}
_resolve(){}
_reject(){}
接下來思考一下_resolve 與_reject兩個函數的作用。我們知道,Promise 根據 then 方法來執行回調,而 then 是根據狀态判斷要執行的回調函數。不難推導出,_resolve 與_reject正是根據handler的執行來進行狀态變更的,而狀态隻能由Pending向Reslove或Rejected轉換,是以有:
...
_resolve(val){//異步傳回的資料
if(this._status==="PENDING"){//保證狀态的不可逆性
this._status="RESOLVED"
this._value=val
_reject(val){
if(this._status==="PENDING"){
this._status="REJECTED"
then的調用邏輯
下面分析 then 函數的邏輯,從調用入手:
then 接收兩個參數,第一個是執行成功調用的函數,第二個是執行失敗調用的函數。
_resolve(val){
then(success, fail){
switch (this._status){
case "PENDING":
break;
case "RESOLVED":
success(this._value)
case "REJECTED":
fail(this._value)
以上實作了最簡單的一個 Promise
測試代碼:
const pro=new SimplePromise(function(res, rej) {
let random=Math.random() * 10
if(random > 5){
res("success")
else{
rej("fail")
})
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
當然,這不能算是一個 Promise,目前僅僅實作了根據狀态調用不同的回調函數。還沒有實作異步。
那如何實作異步呢?關鍵在于 then 函數,當判斷_status為PENDING時,如何延後調用 success與fail函數,等待狀态改變後再調用?
支援異步
這裡采用數組來存儲 fail 與 success 函數:
this.status="PENDING"
this._onSuccess=[]//存儲fail 與 success 函數
this._onFail=[]
handler(this._resolve.bind(this), this._reject.bind(this))
if(this.status==="PENDING"){
let temp
while(this._onSuccess.length > 0){//依次執行onSuccess中的回調函數
temp=this._onSuccess.shift()
temp(val)
while(this._onFail.length > 0){
temp=this._onFail.shift()
then (success, fail){
switch (this.status){
this._onSuccess.push(success)
this._onFail.push(fail)
使用 onSuccess 和 onFail 來存儲回調函數,當處理狀态為 PENDING 時,将回調函數 push 到相應的數組裡,當狀态變更後,依次執行數組裡的回調函數。
setTimeout(function(){
}, 2000)
兩秒後,會執行相應的回調。
到目前為止,最最最簡單的一個 Promise 骨架已經基本完成了。但是還有很多功能待完成。現在可以稍微休息一下,喝個咖啡打個雞血,回來我們會繼續讓這個 Promise 骨架更加豐滿起來。
. . . . . .
完善Promise
歡迎回來,下面我們繼續完善我們的 Promise。
上面完成了一個最基礎的 Promise,然而還遠遠不夠。首先,Promise 需要實作
二手域名購買平台鍊式調用,其次 Promise 還需要實作 all race resolve reject 等靜态函數。
首先,如何實作 then 的鍊式調用呢?需要 then 傳回的也是一個 Promise。
于是有
return new SimplePromise((nextSuccess, nextFail)=> {
const onFullfil=function(val){
const res=success(val)
nextSuccess(res)
const onReject=function(val){
const res=fail(val)
nextSuccess(res) ;
this._onSuccess.push(onFullfil)
this._onFail.push(onReject)
onFullfil(this._value)
onReject(this._value)
const sp=new SimplePromise(function (res, rej){
random > 5 res(random) : rej(random)
}, 1000)
sp.then(data=> {
console.log("more than 5 " + data)
return data
}, err=>{
console.log("less than 5 " + err)
return err
}).then((data)=> {
then的參數限制
完成了鍊式調用,then 方法還有許多其他限制:
下面思考 以下問題:代碼中四個使用 promise 的語句之間的不同點在哪兒?
假設 doSomething 也 doSomethingElse 都傳回 Promise
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
doSomethingElse();
}).then(finalHandler);;
doSomething().then(doSomethingElse()).then(finalHandler);;
doSomething().then(doSomethingElse).then(finalHandler);;
答案 一會兒再揭曉,我們先來梳理以下then 方法對傳入不同類型參數的處理機制:
直接上代碼:
if(typeof success !=="function"){
nextSuccess(val)
const res=success(val)//success 的傳回值
if(res instanceof SimplePromise){//如果success 傳回一個promise 對象
res.then(nextSuccess, nextFail)
if(fail){
if(typeof fail !=="function"){
if(res instanceof SimplePromise){
onReject=function(){}
對于傳入 then 方法的參數,首先判斷其是否為 function,判斷為否,直接執行 下一個 then 的 success 函數;判斷為是,接着判斷函數的傳回值 res 類型是否為 Promise,如果為否,直接執行下一個 then 的 success 函數,如果為是,通過 then 調用接下來的函數。
是以,上面的問題就不難得到答案了。
return doSomethingElse();//傳回值為Promise
RETURN:
doSomething
--->doSomethingElse(undefined)
---> final(doSomethingElseResult)
doSomethingElse();//傳回值為 undefined
---> final(undefined)
doSomething().then(doSomethingElse())//參數 typeof !=function
.then(finalHandler);
doSomethingElse(undefined)
---> final(doSomethingResult)
doSomething().then(doSomethingElse)//與1的調用方式是不同的
--->doSomethingElse(doSomethingResult)
好,then 方法已經完善好了。
靜态函數
接下來是 Promise 的各種靜态函數
class SimplePromise(){
static all(){}
static race(){}
static resolve(){}
static reject(){}
all
static all(promiselist){
if(Array.isArray(promiselist)){
const len=promiselist.length;
const count=0
const arr=[]
return new SimplePromise((res, rej)=> {
for(let i=0; i {
arr[i]=data
count ++
if(count===len){//每一個Promise都執行完畢後傳回
res(arr)
}, err=> {
rej(err)
race
static race(promiselist){
const len=promiselist.length
return new SimplePromise((res, rej)=>{
promiselist.forEach(item=>{
this.resolve(item).then(data=> {
res(data)
resolve
static resolve(obj){
if(obj instanceof SimplePromise){
return obj
else {
return new SimplePromise((res)=>{
res(obj)
reject
static reject(obj){
rej(obj)
總結
現在,一個完整的 Promise 對象就完成了。現在來總結一下 callback 回調和 Promise 的異同吧。
其實,不管是 callback 還是 Promise,這二者都是将需要滞後執行方法而提前聲明的方式,隻不過 callback 的處理方式比較粗犷,将 cb 函數放到異步執行的結尾;而 Promise 優于 cb 的是通過定義了不同的執行狀态,更加細緻的進行結果處理,提供了很好的 catch 機制,這是其一;其二,then 的鍊式調用解決了 cb 的回調地獄;但是 then 的鍊式調用也不是很好的解決方案,如果封裝不好,then裡面套用大量的代碼的話也會引起代碼的不美觀和閱讀上的困難,這一方面的終極解決方法還是 es7 的 async/await。