探祕Promise原理-帶你原生實現Es6之Promise (從0到1)

Week8發表於2018-09-04

Promise是什麼

Promise 是非同步程式設計的一種解決方案。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。

常見傳統非同步解決方案

1、回撥方法

2、事件

3、釋出-訂閱模式

4、觀察者模式

5、其它...

Promise的優勢

比傳統的解決方案——更合理和更強大。

已逐漸成為現在前端程式設計中的一種良好編碼實踐,使其程式碼結構更加合理、可讀性更強。

分步:實現自己的Promise

第一步:從promise的用法中收集它的特點

探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)

/*******************************************************************************

  • 分析總結:
  • 1、Promise是一個類, 接受一個執行函式(excurtor),此執行函式會立即執行
  • 2、excurtor函式接受兩個方法引數, 分別為resolve、reject;呼叫時可以傳入一個值,此值可以是任意值
  • 3、呼叫resolve,會執行then方法的成功回撥(第一個引數)
  • 4、呼叫reject,會執行then方法的失敗回撥(第二個引數)
  • 5、promise有三種狀態:pending(初始狀態)、resolved、rejected;
  • 只能從pending變為resolved或rejeted, 狀態一旦確定,不能再次改變
  • 6、每個例項都有then方法,可以多次then, then方法的引數個數(2個,1個,0個),當then為空時,會將狀態傳遞下去,給下一次then
  • 7、呼叫then時,如果已成功,則執行成功回撥,並把成功的內容傳遞過去;反之,失敗亦然;
  • 8、如果類的執行函式執行丟擲異常時,則會走失敗回撥,且傳入異常
  • 9、呼叫then時,會返回一個新的promise,實現鏈式呼叫;
  • 10、then執行時,如果回撥有返回結果,則會執行下一次的成功回撥,並把值傳入;如果丟擲了異常,則會走下一次的失敗回撥
  • 11、失敗了也可以再成功,如沒有返回值,則返回undefined
  • 12、catch會捕獲沒有捕獲到的錯誤
  • 13、如果回撥中返回的是一個promise, 會等待其執行,來決定走下一次的成功還是失敗回撥
  • 14、自己不能等待自己完成,否則會報一個“迴圈引用”的型別錯誤
  • 15、為什麼連結呼叫時不能返回this? 因為狀態確定後就不能再改變 ******************************************************************************/

第二步:Promise骨架 (簡潔版)

探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)

/* ----------------------------------------------------------------------------
 * Promise 骨架板
 --------------------------------------------------------------------------- */
const states = {
    PENDING  : 'pending',
    FULFILLED: 'fulfilled',
    REJECTED : 'rejected'
}

class Promise {
    /**
     * [ 建構函式 ]
     * @param  {[function]} excurtor [執行函式]
     * @return {[undefined]} undefined
     */
    constructor (excurtor) {
        let self = this;

        this.status      = states.PENDING; // 當前狀態
        this.value       = undefined;      // 成功的內容
        this.reason      = undefined;      // 失敗的原因
        this.onFulfilled = [];             // 儲存成功回撥
        this.onRejected  = [];             // 儲存失敗回撥

        // 成功方法
        function resolve (value) {
            let onFulfilled = self.onFulfilled;

            // 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
            if (self.status === states.PENDING) {
                self.status = states.FULFILLED;
                self.value = value;

                if (onFulfilled.length) {
                    onFulfilled.forEach(fn => {
                        fn();
                    });
                }
            }
        }

        // 失敗方法
        function reject (reason) {
            let onRejected = self.onRejected;

            // 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
            if (self.status === states.PENDING) {
                self.status = states.REJECTED;
                self.reason = reason;

                if (onRejected.length) {
                    onRejected.forEach(fn => {
                        fn()
                    });
                }
            }
        }

        // 立即呼叫執行函式,並捕獲異常
        try {
            excurtor(resolve, reject);
        } catch (e) {
            reject(e); // 捕獲到執行函式異常後,直接執行失敗回撥
        }
    }

    /**
     * [ 例項方法: then ]
     * @param  {[function]} onFulfilled [成功回撥]
     * @param  {[function]} onRejected  [失敗回撥]
     * @return {[undefined]}  undefined
     */
    then (onFulfilled, onRejected) {
        let self = this;

        // 狀態已改變為成功時,則立即執行
        if (self.status === states.FULFILLED) {
            onFulfilled(self.value);
        }

        // 狀態已改變為失敗時,則立即執行
        if (self.status === states.REJECTED) {
            onRejected(self.reason);
        }

        // 處理非同步情況,先存在起來,等狀態改變再執行相應回撥
        if (self.status === states.PENDING) {
            self.onFulfilled.push(() => {
                onFulfilled(self.value);
            });
            self.onRejected.push(() => {
                onRejected(self.reason);
            });
        }
    }
}

// 測試例子 ---------------------------------------------------------------------
let promise = new Promise((resolve, reject) => {
    //setTimeout(() => {
        resolve(100);
    //}, 1000)
});

promise.then(
    value => console.log(value),
    reason => console.log(`fail: ${ reason }`)
);
複製程式碼

第三步:Promise完整版

探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)
探祕Promise原理-帶你原生實現Es6之Promise (從0到1)

/* ----------------------------------------------------------------------------
 * Promise 完整版
 --------------------------------------------------------------------------- */
const states = {
   PENDING  : 'pending',
   FULFILLED: 'fulfilled',
   REJECTED : 'rejected'
}

class Promise {
   /**
    * [ 建構函式 ]
    * @param  {[Function]} excurtor [執行函式]
    * @return {[Undefined]} undefined
    */
   constructor (excurtor) {
       let self = this;

       this.status      = states.PENDING; // 當前狀態
       this.value       = undefined;      // 成功的內容
       this.reason      = undefined;      // 失敗的原因
       this.onFulfilled = [];             // 儲存成功回撥
       this.onRejected  = [];             // 儲存失敗回撥

       // 成功方法
       function resolve (value) {
           let onFulfilled = self.onFulfilled;

           // 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
           if (self.status === states.PENDING) {
               self.status = states.FULFILLED;
               self.value = value;

               if (onFulfilled.length) {
                   onFulfilled.forEach(fn => {
                       fn();
                   });
               }
           }
       }

       // 失敗方法
       function reject (reason) {
           let onRejected = self.onRejected;

           // 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
           if (self.status === states.PENDING) {
               self.status = states.REJECTED;
               self.reason = reason;

               if (onRejected.length) {
                   onRejected.forEach(fn => {
                       fn()
                   });
               }
           }
       }

       // 立即呼叫執行函式,並捕獲異常
       try {
           excurtor(resolve, reject);
       } catch (e) {
           reject(e); // 捕獲到執行函式異常後,直接執行失敗回撥
       }
   }

   /**
    * [ 例項方法: then ]
    * @param  {[Function]} onFulfilled [成功回撥]
    * @param  {[Function]} onRejected  [失敗及異常回撥]
    * @return {[Object]}  promise2
    */
   then (onFulfilled, onRejected) {
       // 補全引數,實現往下傳遞
       onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
       onRejected = typeof onRejected === 'function' ? onRejected : error =>{ throw error };

       let self = this;
       let promise2;

       promise2 = new Promise((resolve, reject) => {
           // 狀態已改變為成功時,則立即執行
           if (self.status === states.FULFILLED) {
               setTimeout(() => { // 此定時器確保回撥方法在非同步中執行的,也方便進行promiseA+規範測試
                   // try catch 用來捕獲回撥方法中丟擲的異常
                   try {
                       let x = onFulfilled(self.value);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch (e) {
                       reject(e);
                   }
               });
           }

           // 狀態已改變為失敗時,則立即執行
           if (self.status === states.REJECTED) {
               setTimeout(() => { // 同上
                    // 同上
                   try {
                       let x = onRejected(self.reason);
                       resolvePromise(promise2, x, resolve, reject);
                   } catch (e) {
                       reject(e);
                   }
               }, 0);
           }

           // 處理非同步情況,先存在起來,等狀態改變再執行相應回撥
           if (self.status === states.PENDING) {
               self.onFulfilled.push(() => {
                   setTimeout(() => {  // 同上
                        // 同上
                       try {
                           let x = onFulfilled(self.value);
                           resolvePromise(promise2, x, resolve, reject);
                       } catch (e) {
                           reject(e);
                       }
                   }, 0);
               });
               self.onRejected.push(() => {
                   setTimeout(() => {  // 同上
                        // 同上
                       try {
                           let x = onRejected(self.reason);
                           resolvePromise(promise2, x, resolve, reject);
                       } catch (e) {
                           reject(e);
                       }
                   }, 0);
               });
           }
       });

       return promise2;
   }

   /**
    * [ 例項方法:catch ]
    * @param  {[Function]} onRejected [失敗及異常回撥]
    * @return {[Object]}   promise
    */
   catch (onRejected) {
       return this.then(null, onRejected);
   }

   /**
    * [ 靜態方法(由類呼叫):all ]
    * @param  {[Array]} promises [由promise組成的陣列]
    * @return {[Object]} promise
    */
   static all (promises) {
       let res = [];
       let count = 0;
       let length = promises.length;

       return new Promise((resolve, reject) => {
           promises.forEach((promise, index) => {
               promise.then(
                   value => {
                       res[index] = value;
                       if (++count === length) {
                           resolve(res);
                       }
                   },
                   reject
               );
           });
       });
   }

   /**
    * [ 靜態方法(由類呼叫)race ]
    * @param  {[Array]} promises [由promise組成的陣列]
    * @return {[Object]} promise
    */
   static race (promises) {
       return new Promise((resolve, reject) => {
          promises.forEach(promise => {
              promise.then(resolve, reject);
          })
       });
   }
}

function resolvePromise(promise2, x, resolve, reject) {
    // 自己不能等待自己的狀態改變
    if (promise2 === x) {
        return reject(new TypeError('迴圈引用'));
    }

    let called; // 標識位,要麼成功,要麼失敗;
    let typeX = typeof x;

    // 判斷x的型別是否為物件或者函式且不為null
    if (x !== null && (typeX === 'object' || typeX === 'function')) {
        try {
            let then = x.then; // 獲取then方法; 此處的try用於防止第三方庫中獲取值會拋錯

            if (typeof then === 'function') { // 說明返回的是promise事例
                // 執行then方法,並將this指向x
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    // 如果返回的還是promise, 則將會繼續遞迴呼叫
                    resolvePromise(promise2, y, resolve, reject);
                }, e => {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else { // x為變通物件時,直接成功
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

// 為了方便promiseA+規範測試
Promise.defer = Promise.deferred = function () {
    let dtd = {};
    dtd.promise = new Promise((resolve, reject) => {
        dtd.resolve = resolve;
        dtd.reject = reject;
    });
    return dtd;
};


module.exports = Promise;



// 測試例子 ---------------------------------------------------------------------
let promise = new Promise((resolve, reject) => {
   resolve(1000);
   //reject(100);
});

promise
.then(
   value => {
       console.log('then:', value);
       return new Promise((resolve, reject) => {
           //resolve(199);
           reject('error123');
       });
   },
   reason => console.log(`fail: ${ reason }`)
)
.then(
   value => console.log(value),
   reason => console.log(`fail: ${ reason }`)
);

Promise.all([
    new Promise((resolve, reject) => {
        resolve(1);
    }),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
        }, 1000);
    }),
    new Promise((resolve, reject) => {
        resolve(3);
        //reject('出錯了')
    })
]).then(
    value => console.log(value),
    e => console.log(e)
);

複製程式碼

Promise/A+ 參考資料及測試外掛

1、PromiseA+規範說明網址

2、promises-aplus-tests

簡介:此包模組可以幫助我們測試自已寫的Promise是否符合上面的規範。

命令: promises-aplus-tests promise.js

說明:命令後的檔名(promise.js) 為你自己定義的檔名

相關文章