一步步寫一個符合Promise/A+規(guī)范的庫

Promise本意是承諾,在程序中的意思就是承諾我過一段時間后會給你一個結(jié)果镶摘。

ES6 中采用了 Promise/A+ 規(guī)范盲憎,Promise 實(shí)現(xiàn)之前纫版,當(dāng)然要先了解 Promise/A+ 規(guī)范凿菩,規(guī)范地址https://promisesaplus.com/机杜。

我們根據(jù) Promise/A+ 規(guī)范,可以寫一個簡單的Promise庫衅谷。

每一步都盡量寫的詳細(xì)椒拗,所以代碼很長很羅嗦。

1.實(shí)現(xiàn)Promise基本的方法

  • Promise是一個類获黔,需要傳遞一個executor函數(shù)蚀苛,函數(shù)里有兩個參數(shù)resolve和reject,調(diào)用resolve代表成功玷氏,調(diào)用reject代表失敗
  • Promise有三種狀態(tài):默認(rèn)狀態(tài)是等待狀態(tài)pending堵未,成功resolved,失敗rejected

使用例子

let Promise = require('./Promise');
// Promise是一個類,需要傳遞一個executor函數(shù),這個函數(shù)我們稱之為執(zhí)行函數(shù),函數(shù)中有兩個參數(shù)resolve和reject他們也是函數(shù)盏触,調(diào)用resolve表示成功渗蟹,調(diào)用reject表示失敗
let promise = new Promise(function(resolve,reject){
    // 成功就不會再調(diào)用失敗,默認(rèn)狀態(tài)是等待狀態(tài)pending
    resolve('ok'); 
    reject('faild');
});
// then是原型上的一個方法接收兩個參數(shù)分別是成功的回調(diào)和失敗的回調(diào)
promise.then(function(data){// 調(diào)用resolve后會執(zhí)行成功的回調(diào),調(diào)用reject后會執(zhí)行失敗的回調(diào)
    console.log(data);
},function(err){
    console.log(err);
});

實(shí)現(xiàn)對應(yīng)的Promise庫代碼

function Promise(executor) {  // Promise中需要接收一個執(zhí)行函數(shù)executor
    let self = this;
    self.status = 'pending'; //默認(rèn)是pending狀態(tài)
    self.value = undefined; // 成功的原因
    self.reason = undefined; // 失敗的原因
    function resolve(value) { // 調(diào)用resolve 會傳入為什么成功
        if(self.status === 'pending'){ // 只有再pending才能轉(zhuǎn)換成功態(tài)
            self.value = value; // 將成功的原因保存下來
            self.status = 'resolved'; // 狀態(tài)改成成功態(tài) 
        }
    }
    function reject(reason) { // 調(diào)用reject會傳入為什么失敗
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
        }
    }
    try {
        executor(resolve, reject);// executor中需要傳入resolve和reject
    } catch (e) {
        // 如果executor執(zhí)行發(fā)生異常赞辩,表示當(dāng)前的promise是失敗態(tài)
        reject(e);
    }
}
// then中要傳入成功的回調(diào)和失敗的回調(diào)
Promise.prototype.then = function(onFufilled,onRejected){
    let self = this;
    // 如果要是成功就調(diào)用成功的回調(diào),并將成功的值傳入
    if(self.status === 'resolved'){
        onFufilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise

2.異步Promise

在new Promise時內(nèi)部可以寫異步代碼拙徽,并且產(chǎn)生的實(shí)例可以then多次

  • 用兩個數(shù)組存放成功和失敗的回調(diào),當(dāng)調(diào)用的時候再執(zhí)行

使用例子

let Promise = require('./Promise');
let promise = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve('ok');
    },1000)
});
// 當(dāng)調(diào)用then時可能狀態(tài)依然是pending狀態(tài),我們需要將then中的回調(diào)函數(shù)保留起來,當(dāng)調(diào)用resolve或者reject時按照順序執(zhí)行
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});

實(shí)現(xiàn)對應(yīng)的Promise庫代碼

function Promise(executor) {  
     self.status = 'pending'; 
     self.value = undefined; 
     self.reason = undefined; 
+    self.onResolvedCallbacks = []; // 成功回調(diào)存放的地方
+    self.onRejectedCallbacks = [];// 失敗回調(diào)存放的地方
     function resolve(value) { 
         if(self.status === 'pending'){ 
             self.value = value; 
             self.status = 'resolved'; 
+            // 依次執(zhí)行成功的回調(diào)
+            self.onResolvedCallbacks.forEach(item=>item());
         }
     }
     function reject(reason) {
         if(self.status === 'pending'){
             self.reason = reason;
             self.status = 'rejected';
+            // 依次執(zhí)行失敗的回調(diào)
+            self.onRejectedCallbacks.forEach(item=>item());
         }
     }
}
Promise.prototype.then = function(onFufilled,onRejected){
     if(self.status === 'rejected'){
         onRejected(self.reason);
     }
+    if(self.status === 'pending'){
+        // 如果是等待態(tài),就將成功和失敗的回調(diào)放到數(shù)組中
+        self.onResolvedCallbacks.push(function(){
+            onFufilled(self.value);
+        });
+        self.onRejectedCallbacks.push(function(){
+            onRejected(self.reason);
+        });
+    }
 }

3.Promise鏈?zhǔn)秸{(diào)用

  • 如果當(dāng)前promise已經(jīng)進(jìn)入成功的回調(diào)诗宣,回調(diào)中發(fā)生了異常返回this的話膘怕,那么當(dāng)前的promise的狀態(tài)無法更改到失敗臺!所以promise實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,返回的并不是this而是一個新的promise召庞。

  • 執(zhí)行回調(diào)中

    • 如果返回的是一個普通的值岛心,會將結(jié)果傳入下一次then的成功回調(diào)中
    • 如果發(fā)生錯誤會被下一次then的失敗回調(diào)捕獲
    • 如果返回的是promise看這個promise是成功還是失敗,對應(yīng)調(diào)用下一次的then

    所以寫一個resolvePromise方法,這是promise中最重要的方法,用來解析then返回的結(jié)果

  • 有些人可能成功失敗同時調(diào)用篮灼,如果兩個都調(diào)用忘古,用第一個調(diào)用的,不允許同時調(diào)用诅诱。

使用例子

promise.then(function(data){
    throw Error('出錯了');// 當(dāng)前promise已經(jīng)成功了髓堪,成功就不會再失敗
    return 'renee' 
}).then(null,function(err){ // 如果返回的是同一個promise那么還怎么走向失敗呢?所以必須要返回一個新的promise
    console.log(err);
})

實(shí)現(xiàn)對應(yīng)的Promise庫代碼

 Promise.prototype.then = function(onFufilled,onRejected){
     let self = this;
+    let promise2; // promise2為then調(diào)用后返回的新promise
     // 如果要是成功就調(diào)用成功的回調(diào),并將成功的值傳入
     if(self.status === 'resolved'){
-        onFufilled(self.value);
+        promise2 = new Promise(function(resolve,reject){
+            try{
+                // 執(zhí)行時有異常發(fā)生,需要將promise2的狀態(tài)置為失敗態(tài)
+                let x = onFufilled(self.value); 
+                // x為返回的結(jié)果
+                // 寫一個方法resolvePromise,是對當(dāng)前返回值進(jìn)行解析,通過解析讓promise2的狀態(tài)轉(zhuǎn)化成成功態(tài)還是失敗態(tài)
+                resolvePromise(promise2,x,resolve,reject);
+            }catch(e){
+                reject(e);
+            }
+        })
     }
     if(self.status === 'rejected'){
-        onRejected(self.reason);
+        promise2 = new Promise(function(resolve,reject){
+            try{
+                let x = onRejected(self.reason);
+                resolvePromise(promise2,x,resolve,reject);
+            }catch(e){
+                reject(e)
+            }
+        })
     }
     if(self.status === 'pending'){
+       promise2 = new Promise(function(resolve,reject){
         self.onResolvedCallbacks.push(function(){
-            onFufilled(self.value);
+                try{
+                    let x = onFufilled(self.value);
+                    resolvePromise(promise2,x,resolve,reject)
+                }catch(e){
+                    reject(e);
+                }
+            })
         });
         self.onRejectedCallbacks.push(function(){
-            onRejected(self.reason);
+                try{
+                    let x = onRejected(self.reason);
+                    resolvePromise(promise2,x,resolve,reject)
+                }catch(e){
+                    reject(e);
+                }
         });
+       })
     }
+    return promise2;
 }
function resolvePromise(promise2, x, resolve, reject) {
    //x是返回的結(jié)果娘荡,如果promise和then中返回的promise是同一個干旁,是不科學(xué)的,要報錯
    if(promise2===x){
        return reject(new Error('循環(huán)引用'))
    }
    if(x!==null&&(typeof x === 'object'|| typeof x === 'function')){
        let called; //表示是否調(diào)用過成功或者失敗
        try{
            let then=x.then;
            //如果then是函數(shù)炮沐,說明是promise争群,我們要讓promise執(zhí)行
            if(typeof then==='function'){
                then.call(x,function(y){
                    if(called)return; //如果調(diào)用過直接return
                    called=true;
                    //如果resolve的結(jié)果依舊是promise那就繼續(xù)解析
                },function(err){
                    if(called) return;
                    called=true;
                    reject(err)
                })
            }else{//如果不是函數(shù),x是一個普通的對象大年,直接成功即可
                resolve(x)
            }
        }catch(e){
            if(called) return;
            called=true;
            reject(e);
        }
    }else{
        //是普通值直接調(diào)用成功
        resolve(x);
    }
}

4.值的穿透

  • 在規(guī)范中定義then函數(shù)可以不傳參,不傳參默認(rèn)會將成功的結(jié)果和失敗的結(jié)果繼續(xù)向下傳遞

使用例子

promise.then().then().then(function(data){
  console.log(data)
})

實(shí)現(xiàn)對應(yīng)的Promise庫代碼

Promise.prototype.then = function (onFufilled, onRejected) {
  //失敗和成功默認(rèn)不傳給一個函數(shù)
+    onFufilled = typeof onFufilled === 'function'?onFufilled:function(value){
+        return value
+    }
+    onRejected = typeof onRejected === 'function'?onRejected:function(err){
+        throw err
+    }

5.測試

另外可以通過安裝一個插件來對實(shí)現(xiàn)的promise進(jìn)行規(guī)范測試换薄。

npm(cnpm) i -g promises-aplus-tests
promises-aplus-tests 文件名
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玉雾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轻要,更是在濱河造成了極大的恐慌复旬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冲泥,死亡現(xiàn)場離奇詭異赢底,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)柏蘑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門幸冻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咳焚,你說我怎么就攤上這事洽损。” “怎么了革半?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵碑定,是天一觀的道長。 經(jīng)常有香客問我又官,道長延刘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任六敬,我火速辦了婚禮碘赖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘外构。我一直安慰自己普泡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布审编。 她就那樣靜靜地躺著撼班,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垒酬。 梳的紋絲不亂的頭發(fā)上砰嘁,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音勘究,去河邊找鬼矮湘。 笑死,一個胖子當(dāng)著我的面吹牛乱顾,可吹牛的內(nèi)容都是我干的板祝。 我是一名探鬼主播宫静,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼走净,長吁一口氣:“原來是場噩夢啊……” “哼券时!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伏伯,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤橘洞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后说搅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炸枣,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年弄唧,在試婚紗的時候發(fā)現(xiàn)自己被綠了适肠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡候引,死狀恐怖侯养,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情澄干,我是刑警寧澤逛揩,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站麸俘,受9級特大地震影響辩稽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜从媚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一逞泄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拜效,春花似錦炭懊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稻励,卻和暖如春父阻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背望抽。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工加矛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人煤篙。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓斟览,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辑奈。 傳聞我的和親對象是個殘疾皇子苛茂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容