JS非同步解決方案的發展流程(三)

凌晨夏沫發表於2018-02-18

為了更好的瞭解promise實現,我們一步步來完善promise庫。我們先來寫一個基礎用例,通過用例來看promise是如何實現的!

1.實現Promise基本方法

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

實現對應的Promise庫程式碼

function Promise(executor) {
    // Promise中需要接收一個執行函式
    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庫程式碼

     self.status = 'pending'; //預設是pending狀態
     self.value = undefined; // 成功的原因
     self.reason = undefined; // 失敗的原因
+    self.onResolvedCallbacks = []; // 成功回撥存放的地方
+    self.onRejectedCallbacks = [];
     function resolve(value) { // 呼叫resolve 會傳入為什麼成功
         if(self.status === 'pending'){ // 只有再pending才能轉換成功態
             self.value = value; // 將成功的原因儲存下來
             self.status = 'resolved'; // 狀態改成成功態 
+            // 依次執行成功的回撥
+            self.onResolvedCallbacks.forEach(item=>item());
         }
     }
     function reject(reason) { // 呼叫reject會傳入為什麼失敗
         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);
+        });
+    }
 }
 module.exports = Promise 
複製程式碼

3.Promise鏈式呼叫

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

promise.then(function(data){
    throw Error('出錯了');// 當前promise已經成功了
    return 'jw'
}).then(null,function(err){ // 如果返回的是同一個promise那麼還怎麼走向失敗呢?所以必須要返回一個新的promise
    console.log(err);
})
複製程式碼
let Promise = require('./Promise');
let promise = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve('ok');
    },1000)
});
promise.then(function(data){
    // 如果返回的是一個普通的值,會將結果傳入下一次then的成功回撥中
    // 如果發生錯誤會被下一次then的失敗回撥捕獲
    // 如果返回的是promise看這個promise是成功還是失敗,對應呼叫下一次的then
    return data+',no problem'; 
},function(err){
    console.log(err);
}).then(function(data){
    console.log(data);
},function(err){
    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;
 }
複製程式碼

3.1 resolvePromise

resolvePromise是promise中最重要的方法,用來解析then返回的結果

function resolvePromise(promise2, x, resolve, reject) {
    // 如果返回的promise和then中返回的promise是同一個promise,根據規範要報型別錯誤
    // 相當於自己等待自己完成這是不科學的
    if (promise2 === x) {
        return reject(new TypeError('迴圈引用'))
    }
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        // 如果是物件可能是一個thenable(帶有then方法)物件
        let called; // 成功或者失敗不能同時呼叫
        try { // 如果用defineProperty定義的then方法獲取時可能會有異常
            let then = x.then;
            // 如果then是函式,說明是promise,我們要讓promse執行
            if (typeof then === 'function') {
                then.call(x, function (y) {
                    if (called) return;
                    called = true;
                    // 如果resolve的結果依舊是promise那就繼續解析
                    resolvePromise(promise2, y, resolve, reject);
                }, 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中的方法非同步執行

為了保證程式執行的一致性,規範中要求then中的方法必須在下一佇列中執行

     if (self.status === 'resolved') {
         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);
-            }
+            setTimeout(function(){
+                try {
+                    // 執行時有異常發生,需要將promise2的狀態置為失敗態
+                    let x = onFufilled(self.value);
+                    // x為返回的結果
+                    // resolvePromise是對當前返回值進行解析,通過解析讓promise2的狀態轉化成成功態還是失敗態
+                    resolvePromise(promise2, x, resolve, reject);
+                } catch (e) {
+                    reject(e);
+                }
+            })
         })
     }
     if (self.status === 'rejected') {
         promise2 = new Promise(function (resolve, reject) {
-            try {
-                let x = onRejected(self.reason);
-                resolvePromise(promise2, x, resolve, reject);
-            } catch (e) {
-                reject(e)
-            }
+            setTimeout(function(){
+                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 () {
-                try {
-                    let x = onFufilled(self.value);
-                    resolvePromise(promise2, x, resolve, reject)
-                } catch (e) {
-                    reject(e);
-                }
+                setTimeout(function(){
+                    try {
+                        let x = onFufilled(self.value);
+                        resolvePromise(promise2, x, resolve, reject)
+                    } catch (e) {
+                        reject(e);
+                    }
+                })
             });
             self.onRejectedCallbacks.push(function () {
-                try {
-                    let x = onRejected(self.reason);
-                    resolvePromise(promise2, x, resolve, reject)
-                } catch (e) {
-                    reject(e);
-                }
+                setTimeout(function(){
+                    try {
+                        let x = onRejected(self.reason);
+                        resolvePromise(promise2, x, resolve, reject)
+                    } catch (e) {
+                        reject(e);
+                    }
+                })
             })
         });
     }
複製程式碼

5.值的穿透

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

Promise.prototype.then = function (onFufilled, onRejected) {
+    onFufilled = typeof onFufilled === 'function'?onFufilled:function(value){
+        return value
+    }
+    onRejected = typeof onRejected === 'function'?onRejected:function(err){
+        throw err
+    }


// 這裡我們可以順便加上catch方法
Promise.prototype.catch = function(callback){
    return this.then(null,callback)
}
複製程式碼

6.Promise中的延遲物件

Promise.defer是Promise的語法糖

Promise.deferred = Promise.defer = function(){
    let dfd = {};
    dfd.promise = new Promise(function(resolve,reject){
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd
}
複製程式碼

看我們可以這樣來使用promise了

function defer(){
    let defer = Promise.defer();
    setTimeout(function(){
        defer.resolve('ok')
    },3000);
    return defer.promise
}
defer().then(function(data){
    console.log(data);
})
複製程式碼

7.resolve接收promise

let Promise = require('./3.async');
new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve(new Promise(function (resolve, reject) {
            resolve('ok');
        }))
    }, 2000)
}).then(function (data) {
    console.log(data);
});
複製程式碼

對resolve方法進行改寫

function resolve(value) { // 呼叫resolve 會傳入為什麼成功
+   if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
+      if(typeof value.then === 'function'){
+           // 將當前promise成功的結果再次傳回resolve函式中
+           return value.then(resolve,reject);
+       }
+   }
    if (self.status === 'pending') {
複製程式碼

8.promise測試

我們可以用promises-aplus-tests庫來測試我們的promise是否符合promiseA+規範

$ npm install promises-aplus-tests -g
promises-aplus-tests Promise.js
複製程式碼

9.Promise.all

Promise.all = function(promises){
    let i = 0; // 用來計數有多少個promise執行成功
    let result = [];// 存放結果
    return new Promise(function(resolve,reject){
        promises.forEach(function(p,index){
            p.then(function(data){
                // 將結果和陣列的索引對應起來
                result[index] = data; 
                if(++i === promises.length){
                    // 當promise全部完成後呼叫all方法成功的回撥
                    resolve(result)
                }
            },reject);
        });
    });
}
複製程式碼

10.Promose.race

任何一個promise成功就變為成功態,失敗就變為失敗態

Promise.race = function (promises) {
    return new Promise(function (resolve, reject) {
        promises.forEach(function (p, index) {
            p.then(function (data) {
                resolve(data)
            }, reject);
        });
    });
}
複製程式碼

11.Promise.resolve

返回一個成功的promise

Promise.resolve = function(value){
    return new Promise(function(resolve,reject){
        resolve(value);
    });
}
複製程式碼

12.Promise.reject

返回一個失敗的promise

Promise.reject = function(reason){
    return new Promise(function(resolve,reject){
        reject(reason);
    });
}
複製程式碼

就此我們實現了Promise的原理,喜歡的點個贊吧^_^!
支援我的可以給我打賞哈!

打賞

相關文章