一步步寫一個符合Promise/A+規範的庫

ReneeTsang發表於2018-03-19

Promise本意是承諾,在程式中的意思就是承諾我過一段時間後會給你一個結果。

ES6 中採用了 Promise/A+ 規範,Promise 實現之前,當然要先了解 Promise/A+ 規範,規範地址promisesaplus.com/

我們根據 Promise/A+ 規範,可以寫一個簡單的Promise庫。

每一步都儘量寫的詳細,所以程式碼很長很羅嗦。

1.實現Promise基本的方法

  • Promise是一個類,需要傳遞一個executor函式,函式裡有兩個引數resolve和reject,呼叫resolve代表成功,呼叫reject代表失敗

  • Promise有三種狀態:預設狀態是等待狀態pending,成功resolved,失敗rejected

使用例子

let Promise = require('./Promise');
// Promise是一個類,需要傳遞一個executor函式,這個函式我們稱之為執行函式,函式中有兩個引數resolve和reject他們也是函式,呼叫resolve表示成功,呼叫reject表示失敗
let promise = new Promise(function(resolve,reject){
    // 成功就不會再呼叫失敗,預設狀態是等待狀態pending
    resolve('ok'); 
    reject('faild');
});
// then是原型上的一個方法接收兩個引數分別是成功的回撥和失敗的回撥
promise.then(function(data){// 呼叫resolve後會執行成功的回撥,呼叫reject後會執行失敗的回撥
    console.log(data);
},function(err){
    console.log(err);
});複製程式碼

實現對應的Promise庫程式碼

function Promise(executor) {  // Promise中需要接收一個執行函式executor
    let self = this;
    self.status = 'pending'; //預設是pending狀態
    self.value = undefined; // 成功的原因
    self.reason = undefined; // 失敗的原因
    function resolve(value) { // 呼叫resolve 會傳入為什麼成功
        if(self.status === 'pending'){ // 只有再pending才能轉換成功態
            self.value = value; // 將成功的原因儲存下來
            self.status = 'resolved'; // 狀態改成成功態 
        }
    }
    function reject(reason) { // 呼叫reject會傳入為什麼失敗
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
        }
    }
    try {
        executor(resolve, reject);// executor中需要傳入resolve和reject
    } catch (e) {
        // 如果executor執行發生異常,表示當前的promise是失敗態
        reject(e);
    }
}
// then中要傳入成功的回撥和失敗的回撥
Promise.prototype.then = function(onFufilled,onRejected){
    let self = this;
    // 如果要是成功就呼叫成功的回撥,並將成功的值傳入
    if(self.status === 'resolved'){
        onFufilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise複製程式碼

2.非同步Promise

在new Promise時內部可以寫非同步程式碼,並且產生的例項可以then多次

  • 用兩個陣列存放成功和失敗的回撥,當呼叫的時候再執行

使用例子

let Promise = require('./Promise');
let promise = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve('ok');
    },1000)
});
// 當呼叫then時可能狀態依然是pending狀態,我們需要將then中的回撥函式保留起來,當呼叫resolve或者reject時按照順序執行
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});
promise.then(function(data){
    console.log(data);
},function(err){
    console.log(err);
});複製程式碼

實現對應的Promise庫程式碼

function Promise(executor) {  
     self.status = 'pending'; 
     self.value = undefined; 
     self.reason = undefined; 
+    self.onResolvedCallbacks = []; // 成功回撥存放的地方
+    self.onRejectedCallbacks = [];// 失敗回撥存放的地方
     function resolve(value) { 
         if(self.status === 'pending'){ 
             self.value = value; 
             self.status = 'resolved'; 
+            // 依次執行成功的回撥
+            self.onResolvedCallbacks.forEach(item=>item());
         }
     }
     function reject(reason) {
         if(self.status === 'pending'){
             self.reason = reason;
             self.status = 'rejected';
+            // 依次執行失敗的回撥
+            self.onRejectedCallbacks.forEach(item=>item());
         }
     }
}
Promise.prototype.then = function(onFufilled,onRejected){
     if(self.status === 'rejected'){
         onRejected(self.reason);
     }
+    if(self.status === 'pending'){
+        // 如果是等待態,就將成功和失敗的回撥放到陣列中
+        self.onResolvedCallbacks.push(function(){
+            onFufilled(self.value);
+        });
+        self.onRejectedCallbacks.push(function(){
+            onRejected(self.reason);
+        });
+    }
 }複製程式碼

3.Promise鏈式呼叫

  • 如果當前promise已經進入成功的回撥,回撥中發生了異常返回this的話,那麼當前的promise的狀態無法更改到失敗臺!所以promise實現鏈式呼叫,返回的並不是this而是一個新的promise。

  • 執行回撥中

    • 如果返回的是一個普通的值,會將結果傳入下一次then的成功回撥中

    • 如果發生錯誤會被下一次then的失敗回撥捕獲

    • 如果返回的是promise看這個promise是成功還是失敗,對應呼叫下一次的then

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

  • 有些人可能成功失敗同時呼叫,如果兩個都呼叫,用第一個呼叫的,不允許同時呼叫。

使用例子

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

實現對應的Promise庫程式碼

 Promise.prototype.then = function(onFufilled,onRejected){
     let self = this;
+    let promise2; // promise2為then呼叫後返回的新promise
     // 如果要是成功就呼叫成功的回撥,並將成功的值傳入
     if(self.status === 'resolved'){
-        onFufilled(self.value);
+        promise2 = new Promise(function(resolve,reject){
+            try{
+                // 執行時有異常發生,需要將promise2的狀態置為失敗態
+                let x = onFufilled(self.value); 
+                // x為返回的結果
+                // 寫一個方法resolvePromise,是對當前返回值進行解析,通過解析讓promise2的狀態轉化成成功態還是失敗態
+                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是返回的結果,如果promise和then中返回的promise是同一個,是不科學的,要報錯
    if(promise2===x){
        return reject(new Error('迴圈引用'))
    }
    if(x!==null&&(typeof x === 'object'|| typeof x === 'function')){
        let called; //表示是否呼叫過成功或者失敗
        try{
            let then=x.then;
            //如果then是函式,說明是promise,我們要讓promise執行
            if(typeof then==='function'){
                then.call(x,function(y){
                    if(called)return; //如果呼叫過直接return
                    called=true;
                    //如果resolve的結果依舊是promise那就繼續解析
                },function(err){
                    if(called) return;
                    called=true;
                    reject(err)
                })
            }else{//如果不是函式,x是一個普通的物件,直接成功即可
                resolve(x)
            }
        }catch(e){
            if(called) return;
            called=true;
            reject(e);
        }
    }else{
        //是普通值直接呼叫成功
        resolve(x);
    }
}複製程式碼

4.值的穿透

  • 在規範中定義then函式可以不傳參,不傳參預設會將成功的結果和失敗的結果繼續向下傳遞

使用例子

promise.then().then().then(function(data){
  console.log(data)
})複製程式碼

實現對應的Promise庫程式碼

Promise.prototype.then = function (onFufilled, onRejected) {
  //失敗和成功預設不傳給一個函式
+    onFufilled = typeof onFufilled === 'function'?onFufilled:function(value){
+        return value
+    }
+    onRejected = typeof onRejected === 'function'?onRejected:function(err){
+        throw err
+    }複製程式碼

5.測試

另外可以通過安裝一個外掛來對實現的promise進行規範測試。

npm(cnpm) i -g promises-aplus-tests
promises-aplus-tests 檔名複製程式碼


相關文章