上一篇文章 JavaScript Promise基礎(對於 Promise用法不熟悉的,能夠先看這篇文章,理解了再來看這篇文章,會對你有很大幫助)中,介紹了Promise的基本使用,在這篇文章中,咱們將根據 Promises/A+ 規範試着本身來寫一個Promise,主要是學習Promise的內部機制與它的編程思想。javascript
Promise究竟是啥玩意呢?是一個類、數組、對象、函數?好了不猜了,打印出來看看吧console.dir(Promise),是騾子是馬拉出來遛遛唄! java
呀呀呀!原來 Promise 是一個構造函數,本身身上有 all、resolve、reject,原型上有 then、catch 等眼熟的方法。那就不用廢話了 new 一個玩玩唄!git
let p=new Promise((resolve,reject)=>{
console.log(1);
resolve('成功');
reject('失敗');
});
console.log(2);
p.then((data)=>{
console.log(data+1);
},(err)=>{
console.log(err);
})
p.then((data)=>{
console.log(data+2);
},(err)=>{
console.log(err);
})
複製代碼
輸出 1 2 成功1 成功2github
new Promise時傳遞一個函數(executor執行器是當即執行的),並接收兩個參數:resolve,reject,分別表示成功的回調函數與失敗的回調函數。每個實例都有一個 then 方法,參數是成功和失敗,成功會有成功的值,失敗會有失敗的緣由,而且成功就不能失敗反之也同樣。同一個 Promise 能夠屢次 then……貌似跑偏了呀!大家都懂也應該會用,仍是回到主題開始本身實現吧!npm
class Promise{
constructor(executor){
this.status='pending'; //默認的狀態
this.value=undefined; //默認成功的值
this.reason=undefined; //默認失敗的緣由
this.onResolvedCallbacks=[];//存放成功的數組
this.onRejectedCallbacks=[];//存放失敗的數組
let resolve=(value)=>{
if(this.status==='pending'){//這裏的判 斷是爲了防止executor中調用兩次resovle或reject方法
this.status='resolved';//成功了
this.value=value;//成功的值
}
}
let reject=(reason)=>{
if(this.status==='pending'){
this.status='rejected';//失敗了
this.reason=reason;//失敗的緣由
}
}
try {//捕獲異常
executor(resolve,reject);//默認讓執行器執行
} catch (err) {
reject(err)
}
}
then(onFufilled,onRejected){
if(this.status==='resolved'){
onFufilled(this.value);
}
if(this.status==='rejected'){
onRejected(this.reason);
}
}
}
let p=new Promise((resolve,reject)=>{
resolve('成功');
//reject('失敗');
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
複製代碼
執行輸出成功,那麼問題來了,咱們費勁半天寫 Promise 上來就執行有啥用?使用 Promise 時咱們通常會寫一些異步代碼,等到異步操做執行完纔會觸發 resolve 或者 reject 函數。但是如今當執行 then 方法的時候此時的狀態仍是初始的pending 狀態,因此爲了能取到參數,咱們能夠經過發佈訂閱模式來實現。編程
then 方法裏添加以下代碼,當狀態爲 pending 時,咱們先把回調函數存到對應的數組裏,等待調用。數組
if(this.status==='pending'){
this.onResolvedCallbacks.push(()=>{
onFufilled(this.value);
});
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
})
}
複製代碼
resolve 和 reject 方法裏分別添加以下代碼,當調用的時候,把對應數組裏的函數依次執行promise
this.onResolvedCallbacks.forEach(fn=>fn());
this.onRejectedCallbacks.forEach(fn=>fn());
複製代碼
咱們都知道 Promise 有一個最爲重要的 then 方法。爲了保證鏈式調用, then 方法調用後返回一個新的 Promise,會將這個 Promise 的值傳遞給下一次 then 中。而且上一次 then 中不論是成功仍是失敗或是返回一個普通值,都會傳遞到下一次 then 的參數。異步
首先咱們知道,then 是有返回值的。並且能夠一直 then 下去,因此以前的 then 必須返回一個新的 Promise。因此咱們根據Promises/A+對 then 方法改造以下。函數
then(onFufilled,onRejected){
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
let x=onFufilled(this.value);
//判斷p是否是一個promise,若是是取它的結果做爲promise2成功的結果,
//若是返回一個普通值,一樣做爲promise2成功的結果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之間的關係
});
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
}
if(this.status==='pending'){//當前既沒有成功,也沒有失敗
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回調
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}
);
this.onRejectedCallbacks.push(()=>{//存放失敗的回調
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
})
}
return promise2;//調用then後返回一個新的promise
}
複製代碼
resolvePromise 是幹啥的呢?因爲 then 可能返回任意值,因此根據Promises/A+規範對 then 返回的值進行以下處理或解析。
function resolvePromise(promise2,x,resolve,reject){
//判斷x是否是promise
//若是當前返回的promise和x引用同一個對象報類型錯誤(不能本身等待本身完成)
if(promise2===x){
return reject(new TypeError('循環引用'));
}
//x不是null而且是對象或函數時,多是promise
if(x!==null&&(typeof x==='object'|| typeof x==='function')){
let called; //標識當前promise有沒有調用過
try{//儘可能讓別人瞎寫,防止取then時出現異常
let then=x.then;//取x的then看是否是函數
if(typeof then==='function'){//若是是函數就認爲它是promise
then.call(x,(y)=>{//第一個參數是this,後面的是成功的回調和失敗的回調
if(called) return;
called=true;
resolvePromise(promise2,y,resolve,reject);//若是y是promise繼續遞歸解析
},(err)=>{//只要有一個失敗了就失敗了
if(called) return;
called=true;
reject(err);
})
}else{//then是一個普通對象直接成功
resolve(x);
}
}catch(e){
if(called) return;
called=true;
reject(e);
}
}else{//若是x是普通值直接成功
resolve(x);
}
}
複製代碼
咱們用 Promise 時發現,當不給 then 中傳入參數時,後面的 then 依舊能夠獲得以前 then 的返回值。例如:p.then().then(),這就是值的穿透。
then(onFufilled,onRejected){
//解決onFufilled或onRejected沒有傳的問題
onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
let x=onFufilled(this.value);
//判斷p是否是一個promise,若是是取它的結果做爲promise2成功的結果,
//若是返回一個普通值,一樣做爲promise2成功的結果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之間的關係
});
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
}
if(this.status==='pending'){//當前既沒有成功,也沒有失敗
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回調
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}
);
this.onRejectedCallbacks.push(()=>{//存放失敗的回調
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
})
}
return promise2;//調用then後返回一個新的promise
}
複製代碼
executor 執行的時候咱們在外面包了 try{}catech 可是咱們內部代碼是異步的,就沒法捕獲錯誤了,須要給每一個 then 中的方法都加一個 try{}catch
then(onFufilled,onRejected){
//解決onFufilled或onRejected沒有傳的問題
onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
setTimeout(() => {
try{
let x=onFufilled(this.value);
//判斷p是否是一個promise,若是是取它的結果做爲promise2成功的結果,
//若是返回一個普通值,一樣做爲promise2成功的結果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之間的關係
}catch(e){
reject(e);
}
}, 0);
});
// return promise2;
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
setTimeout(() => {
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
})
// return promise2;
}
if(this.status==='pending'){//當前既沒有成功,也沒有失敗
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回調
setTimeout(() => {
try{
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(()=>{//存放失敗的回調
setTimeout(() => {
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
})
})
// return promise2;
}
return promise2;//調用then後返回一個新的promise
}
複製代碼
catch 接收的參數只有錯誤,也就至關於 then 方法沒有成功的簡寫。並且 catch 後依然能夠 then,那就簡單暴力上代碼吧!
catch(onRejected){
return this.then(null,onRejected);
}
複製代碼
Promise.resolve()、Promise.reject() 這兩種用法,是直接能夠經過類調用的,原理就是返回一個內部是resolve 或 reject 的 Promise 對象。
Promise.resolve=function(val){
return new Promise((resolve,reject)=>{
resolve(val)
})
}
Promise.reject=function(val){
return new Promise((resolve,reject)=>{
reject(val)
})
}
複製代碼
all方法的做用就是將一個數組的 Promise 對象放在其中,當所有 resolve 的時候就會執行 then 方法,當有一個 reject 的時候就會執行 catch,而且他們的結果也是按着數組中的順序來的.
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
}
}
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject)
}
})
}
複製代碼
寫了這麼多,到底符不符合Promises/A+規範呢?
//promise的語法糖
Promise.defer=Promise.deferred=function(){
let dfd={};
dfd.promise=new Promise((resolve,reject)=>{
dfd.resolve=resolve;
dfd.reject=reject;
})
return dfd;
}
複製代碼
安裝promises-aplus-tests用來測試,安裝:npm install promises-aplus-tests -g,測試:promises-aplus-tests + "文件名"。
到這基本就簡單實現了一個本身的 Promise,此時對 Promise 的內部機制與它的編程思想有沒有更深刻的理解呢?新手可能一臉懵逼,大牛可能一臉蔑視。但願你們都有收穫。寫的很差,有問題歡迎你們在評論區評論指正(還不快去點贊⊙﹏⊙)!!!