扒一扒PROMISE的原理,你們不要怕!

在前端的平常工做中,回調函數(callback)應該是見怪不怪了,可是當回調函數趕上了異步(async),這就使人髮指了。那麼異步是什麼意思呢,簡單地說就是不等你執行完,就先執行下方的代碼了。前端

舉個🌰:git

咱們最經常使用的異步操做應該是ajax了(想當初我第一次用ajax的時候,簡直就是災難。明明資源加載成功了,怎麼就是沒有調到資源中的數據呢?真使人頭大啊。),只能等待加載完畢,再執行相關操做才能成功。所以咱們看到的代碼應該都是這樣的。github

/**
@param callback 回調函數
*/
function getData(url,callback){
    $.ajax({
        url:url,
        success:function(result){
            callback(result);
        }
    });
}
//假設我有好多個ajax,每一個ajax都須要上一個ajax的支持,因而……地獄出現了……
getData(url1,function(res){
    getData(url2,function(res){
        getData(url3,function(res){
            //終於能夠幹正事了
        })
    })
})
複製代碼

朋友們,回調地獄(callback Hell)瞭解下。ajax

因而promise出現了,他的出現就是解決了回調地獄!他對異步的函數進行了封裝,把回調變成了鏈式調用。promise

舉個🌰:bash

function getData(url){
    return new Promise((resolve,reject)=>{
$.ajax({
        url:url,
        success:function(result){
            resolve(result);
        },
        error:function(error){
            reject(error);
        }
    });
    })
}
getData(url1).then(function(res){
    return getData(url2)
}).then(function(res){
    return getData(url3)
}).then(function(res){
    //幹正事啦!
})

複製代碼

確實。簡潔了很多,至少不會被裏三層外三層的括號弄暈。異步

可是當初我聽到promise的時候,我心裏是拒絕的。雖然心裏拒絕,可是該來的仍是要來的,該學的仍是要學的,畢竟時代在進步,與時俱進仍是很必要的!那麼這個promise是怎麼實現的呢???async

實現一:狀態

小夥伴們,這裏promise可不是男女約會中浪漫的臺詞 」I promise XXX「 ,而是一種規範,點擊此處獲取規範。不過這裏的promise和現實生活中的promise同樣,都有實現(fulfilled),拒絕(rejected)和等待(pending)這三種狀態。函數

舉個🌰:性能

假定 Mary 和 Mike 是一對情侶,半年前,Mike 向 Mary 承諾(promise)半年內完成他們的婚禮,可是直到如今 Mike 也沒有作出行動,所以 Mary 表示她不會一直等待(pending)下去,因而他們分手了,那麼這個承諾(promise)就是做廢了(rejected)。若是這半年內 Mike 和 Mary 結了婚,那麼如今 Mike 應該已經實現(fulfilled)了他對 Mary 的承諾(promise)。

因此說,全部的promise都有一個結果狀態——實現(fulfilled)或者拒絕(rejected),而結果出來以前的狀態就是等待(pending)。

p1.js

//p1.js
function Promise(executor){
    let _=this;
    _.value=undefined;
    _.reason=undefined;
    _.state="pending"//你們一開始都是同樣,等着吧
    function resolve(value){
        _.value=value//實現以後的感言
        _.state="fulfilled"//實現啦!
    }
    function reject(reason){
        _.reason=reason //給我一個被拒絕的理由
        _.state="rejected" //被拒絕了!
    }
    executor(resolve,reject)
}

//e.g
let Iagree=new Promise((resolve,reject)=>{
    resolve("我開心就贊成了");//
})
let Idisagree=new Promise((resolve,reject)=>{
    reject("我不開心就拒絕了");
})
let noResult=new Promise((resolve,reject)=>{
})
console.log(Iagree.state,Idisagree.state,noResult.state)
複製代碼

實現二:添加then函數

不過我只知道一個狀態有何用?我還要進行下一步噠!咱們須要一個then,用於進行下一步的操做。

p2.js

//p2.js
Promise.prototype.then=function(onFulfilled, onRejected){
    let _=this;
    if(_.state=="pending"){}
    if(_.state=="fulfilled"){
        onFulfilled(_.value)
    }
    if(_.state=="rejected"){
        onRejected(_.reason)
    }
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
    resolve("我開心就贊成了");//強行完成(fullfilled)
})
Iagree.then((data)=>{
    console.log(Iagree.state)
},(e)=>{
    console.log(e)
})
複製代碼

實現三:實現異步執行

不過這個都是同時進行,不是異步的。咱們來瞅一眼異步~

這個時候咱們須要把回調函數丟到resolve或者reject中,可是若是咱們的後續方法不少呢?then好屢次怎麼辦!將回調丟到的隊列中,到時候Foreach一下逐個執行。

p3.js

//p3.js
function Promise(executor){
    //....
    _.resolveCallbacks=[];//callbacks在pending中添加,fullfilled中執行
    _.rejectCallbacks=[];//callbacks在pending中添加,rejected中執行
    function resolve(value){
        //....
        _.resolveCallbacks.forEach((fn)=>fn())
    }
    function reject(reason){
       //....
        _.rejectCallbacks.forEach((fn)=>fn())
    }
    //....
}
Promise.prototype.then=function(onFulfilled, onRejected){
    let _=this;
    if(_.state=="pending"){
        //把回調方法塞進隊列中
        _.resolveCallbacks.push(()=>{
            onFulfilled(_.value)
        })
        _.rejectCallbacks.push(()=>{
            onRejected(_.reason)
        })
    }
    //....
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve("我開心就贊成了");
    },1000)
})

//爲了防止屢次then,因此回調方法須要丟入隊列中,防止方法被覆蓋。
Iagree.then((data)=>{
    console.log(Iagree.state)
},(e)=>{
    console.log(e)
})
Iagree.then((data)=>{
    console.log(Iagree.state+1)
},(e)=>{
    console.log(e)
})
複製代碼

實現四:實現鏈式調用

那麼問題來了,若是我直接then,可不能夠?像這這樣:

Iagree.then((data)=>{
    ...
}).then((data)=>{
    ...
}).then((data)=>{
    ...
})
複製代碼

若是想要這樣寫,那麼上一步的then必須返回一個promise對象才能夠,否則哪裏變出一個then方法。所以咱們須要在thennew一個新的promise,用於下一個鏈式調用的then

p4.js

//p4.js
function resolvePromise(promise,x,resolve,reject){
    //若是x多是一個promise
    if(x!==null&&(typeof x==="object"||typeof x==="function")){ 
        let then=x.then;
        //若是x是一個promise,由於promise都要有then函數的
        if(typeof then === "function"){
            //y表示x這個promise的值
            then.call(x,y=>{
                //繼續遍歷,直至返回值不是promise
                resolvePromise(promise,y,resolve,reject)
            },err=>{
                reject(err)
            })
        }else{
            //若是x是個普通對象,直接運行
            resolve(x)
            }
    }else{
        //若是x不是一個promise,也就是x是一個常量,直接運行
        resolve(x)
    }
}
Promise.prototype.then=function(onFulfilled, onRejected){
    let _=this;
    let promise2;
    //將當前promise的值傳遞到下一次then的調用中
    function resolveFunction(promise,resolve,reject){
        let x=onFulfilled(_.value)
        resolvePromise(promise,x,resolve,reject)
    }
    function rejectFunction(promise,resolve,reject){
        let x=onRejected(_.reason)
        resolvePromise(promise,x,resolve,reject)
    }
    promise2=new Promise((resolve,reject)=>{
        if(_.state=="pending"){
            //把回調方法塞進隊列中
            _.resolveCallbacks.push(()=>{
                resolveFunction(promise2,resolve,reject)
            })
            _.rejectCallbacks.push(()=>{
                rejectFunction(promise2,resolve,reject)
            })
        }
        if(_.state=="fulfilled"){
            resolveFunction(promise2,resolve,reject)
        }
        if(_.state=="rejected"){
            rejectFunction(promise2,resolve,reject)
        }
    })
    return promise2
}
//e.g
let Iagree=new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve("我開心就贊成了");
    },1000)
})

//爲了防止屢次then,因此回調方法須要丟入隊列中,防止方法被覆蓋。
Iagree.then((data)=>{
    console.log(data)
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("看心情幹活");
        },1000)
    })
}).then((data)=>{
    console.log("前方返回一個promise:"+data)
    return data+",我是一個常量"
}).then((data)=>{
    console.log("常量返回:"+data)
}).then((data)=>{
    console.log("前方沒法返回:"+data)
})
複製代碼

這樣咱們就能夠愉快地用鏈式調用promise了,想一想就美滋滋。 不過以上只是簡單粗暴的實現promise的方式,只是一個原理,還有promise的一些規範須要完善點擊此處獲取規範

符合promisesA+的規範

總結幾點

  • try{}catch(){}的地方都標記上,寧肯錯殺不放過。
  • onFulfilled和onRejected的方法放入setTimeout之中,爲了讓他們變成「宏任務(macro-task)」。(應該是出於性能的考慮,以後再研究。)
  • 而後加一個Promise.defer = Promise.deferred = function(){}方法,防止篡改。
  • 接着導出promise,module.exports=Promise
  • 最後運行一波promises-aplus-tests.cmd 你的promise.js,而後一行行地檢查你的代碼,等到所有變綠(passing),恭喜你成功攻克promise!!

p5.js

//參考p5.js
複製代碼
相關文章
相關標籤/搜索