來,用ES6寫個Promise吧

本文采用es6語法實現Promise基本的功能, 適合有javascript和es6基礎的讀者,若是沒有,請閱讀javascript

  • http://es6.ruanyifeng.com/

回調函數

在平常開發中,一般會使用ajax請求數據,拿到數據後,對數據進行處理。java

可是,假設你須要屢次ajax請求數據,而且每次ajax請求數據都是基於上一次請求數據做爲參數,再次發出請求,因而代碼可能就成了這樣:es6

function dataAjax() {
    $.ajax({
        url: '/woCms/getData',
        type: 'get',
        success: function (data) {
            reChargeAjax(data);
        }
    });
}
function reChargeAjax(data) {
    $.ajax({
        url: '/woCms/reCharge',
        type: 'post',
        data: { data },
        success: function (data) {
            loginAjax(data);
        }
    });
}
....
複製代碼

很明顯,這樣的寫法有一些缺點:ajax

  • 若是需求存在多個操做而且層層依賴的話,那這樣的嵌套就可能存在多層,而且每次都須要對返回的數據進行處理,這樣就嚴重加大了調試難度,代碼不是很直觀,而且增長了後期維護的難度
  • 這種函數的層層嵌套,又稱之爲回調地域,爲了解決這種形式存在的缺點,並支持多個併發的請求,因而Promise就出現了

Promise是什麼

  • Promise是一種異步流程的控制手段。
  • Promise對象可以使咱們更合理、更規範的處理異步流程操做.

Promise基本用法

let pro = new Promise(function (resolve, reject) {
});
複製代碼
  • Promise是一個全局對象,也就是一個類
  • 可經過new關鍵詞建立一個Promise實例
  • Promise構造函數傳遞一個匿名函數, 匿名函數有2個參數: resolve和reject
  • 兩個參數都是方法,resolve方法處理異步成功後的結果
  • reject處理異步操做失敗後的緣由

Promise的三種狀態

  • pending: Promise 建立完成後,默認的初始化狀態
  • fulfilled: resolve方法調用時,表示操做成功
  • rejected:reject方法調用時,表示操做失敗

狀態只能從初始化 -> 成功或者初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。promise

Promise 類構造

好了,目前咱們知道了Promise的基礎定義和語法,那麼咱們就用代碼來模擬Promise的構造函數和內部實現吧bash

class Promise {
    // 構造函數,構造時傳入一個回調函數
    constructor(executor) {
        this.status = "pending";// promise初始化狀態爲pending
        this.value = undefined;// 成功狀態函數的數據
        this.reason = undefined;// 失敗狀態的緣由

        // new Promise((resolve,reject)=>{});
        let resolve = data => {
            // 成功狀態函數,只有當promise的狀態爲pending時才能修改狀態
            // 成功或者失敗狀態函數,只能調用其中的一個
            if (this.status === "pending") {
                this.status = "fulfilled";
                this.value = data;
            }
        }
        let reject = err => {
            if (this.status === "pending") {
                this.status = "rejected";
                this.reason = err;
            }
        }
    }
    executor(resolve, rejcte);
}
複製代碼

在Promise的匿名函數中傳入resolve, rejcte這兩個函數,分別對應着成功和失敗時的處理,根據Promise規範,只有當Promise的狀態是初始化狀態是才能修改其狀態爲成功或者失敗,而且只能轉爲成功或者失敗。併發

then方法

在瞭解Promise建立和狀態以後,咱們來學習Promise中一個很是重要的方法:then方法異步

then()方法:用於處理操做後的程序,那麼它的語法是什麼樣子的呢,咱們一塊兒來看下函數

pro.then(function (res) {
    //操做成功的處理
}, function (error) {
    //操做失敗的處理
}); 
複製代碼

那麼then函數該如何定義呢?post

很明顯,then屬於Promise原型上的方法,接收2個匿名函數做爲參數,第一個是成功函數,第二個是失敗函數。

也就是說當Promise狀態變爲成功的時候,執行第一個函數。當狀態變爲失敗的時候,執行第二個函數。所以then函數能夠這樣定義:

then(onFulFiled, onRejected) {
    // promise 執行返回的狀態爲成功態
    if (this.status === "fulfilled") {
        // promise執行成功時返回的數據爲成功態函數的參數
        onFulFiled(this.value);
    }
    if (this.status === "rejected") {
        // promise 執行失敗是返回的緣由爲失敗態函數的參數
        onRejected(this.reason);
    }
}
複製代碼

Promise then 異步代碼隊列執行

可是咱們如今來看這樣一段Promise代碼:

let pro = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve("suc");
    }, 1000);
});
// 同一個promise實例屢次調用then函數
pro.then(data => {
    console.log("date", data);
}, err => {
    console.log("err", err);
});
pro.then(data => {
    console.log("date", data);
}, err => {
    console.log("err", err);
})

輸出結果爲
date suc
date suc
複製代碼

這樣Promise中異步調用成功或者失敗狀態函數,而且Promise實例屢次調用then方法,咱們能夠看到最後輸出屢次成功狀態函數中的內容,而此時:

  • Promise中異步執行狀態函數,但Promise的then函數是仍是pending狀態
  • Promise實例pro屢次調用then函數,當狀態是pending的時候依然會執行狀態函數

那麼這塊在then函數中是如何處理pending狀態的邏輯呢?

採用發佈訂閱的方式,將狀態函數存到隊列中,以後調用時取出。

class Promise{
    constructor(executor){
        // 當promise中出現異步代碼將成功態或者失敗態函數封裝時
        // 採用發佈訂閱的方式,將狀態函數存到隊列中,以後調用時取出
        this.onFuiFiledAry = [];
        this.onRejectedAry = [];

        let resolve = data => {
            if (this.status === "pending") {
                ...
                // 當狀態函數隊列中有存放的函數時,取出並執行,隊列裏面存的都是函數
                this.onFulFiledAry.forEach(fn => fn());
            }
        }
        let reject = err => {
            if (this.status === "pending") {
                ...
                this.onRejectedAry.forEach(fn => fn());
            }
        }
    }
    then(onFuiFiled, onRejected) {
        ...
        if (this.status === "pending") {
            this.onFuiFiledAry.push(() => {
                onFuiFiled(this.value);
            });
            this.onRejectedAry.push(() => {
                onRejected(this.reason);
            });
        }
    }
}
複製代碼

Promise then 函數返回一個新的Promise

看過jQuery源碼的童鞋確定都清楚,jQuery是如何實現鏈式調用的呢?對,就是this,jQuery經過在函數執行完成後經過返回當前對象的引用,也就是this來實現鏈式調用。

咱們先看看一個例子,假設Promise用this來實現鏈式調用,會出現什麼狀況呢?

let pro = new Promise(function (resolve, reject) {
    resolve(123);
});
let p2 = pro.then(function () {
    // 若是返回this,那p和p2是同一個promise的話,此時p2的狀態也應該是成功
    // p2狀態設置爲成功態了,就不能再修改了,可是此時拋出了異常,應該是走下個then的成功態函數
    throw new Error("error");
});
p2.then(function (data) {
    console.log("promise success", data);
}, function (err) {
    // 此時捕獲到了錯誤,說明不是同一個promise,由於promise的狀態變爲成功後是不能再修改狀態
    console.log("promise or this:", err);
})
複製代碼

很明顯,Promise發生異常,拋出Error時,p2實例的狀態已是失敗態了,因此會走下一個then的失敗態函數,而結果也正是這樣,說明Promise並非經過this來實現鏈式調用。

那Promise中的鏈式調用是如何實現的呢?

結果是,返回一個新的Promise.

then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            onFuiFiled(this.value);
        });
    }
    if (this.status === "rejected") {
        newpro = new Promise((resolve, reject) => {
            onRejected(this.reason);
        });
    }
    if (this.status === "pending") {
        newpro = new Promise((resolve, reject) => {
            this.onFuiFiledAry.push(() => {
                onFuiFiled(this.value);
            });
            this.onRejectedAry.push(() => {
                onRejected(this.reason);
            });
        });
    }
    return newpro;
}
複製代碼

Promise then函數返回值解析

好了,咱們繼續將目光放在then函數上,then函數接收兩個匿名函數,那假設then函數返回的是數值、對象、函數,或者是promise,這塊Promise又是如何實現的呢?

來,咱們先看例子:

let pro = new Promise((resolve, reject) => {
    resolve(123);
});
pro.then(data => {
    console.log("then-1",data);
    return 1;
}).then(data => {
    console.log("then-2", data);
});
複製代碼

例子輸出的結果是 then-1 123 then-2 1

也就是說Promise會根據then狀態函數執行時返回的不一樣的結果來進行解析:

  • 若是上一個then函數返回的是數值、對象、函數的話,是會直接將這個數值和對象直接返回給下個then;
  • 若是then返回的是null,下個then獲取到的也是null;
  • 若是then返回的是一個新的promise的話,則根據新的promise的狀態函數來肯定下個then調用哪一個狀態函數,若是返回的新的promise沒有執行任何狀態函數的話,則這個promise的狀態是pending

那這塊,咱們該如何實現呢?來,看具體代碼吧

function analysisPromise(promise, res, resolve, reject) {
    // promise 中返回的是promise實例自己,並無調用任何的狀態函數方法
    if (promise === res) {
        return reject(new TypeError("Recycle"));
    }
    // res 不是null,res是對象或者是函數
    if (res !== null && (typeof res === "object" || typeof res === "function")) {
        try {
            let then = res.then;//防止使用Object.defineProperty定義成{then:{}}
            if (typeof then === "function") {//此時當作Promise在進行解析
                then.call(res, y => {
                    // y做爲參數,promise中成功態的data,遞歸調用函數進行處理
                    analysisPromise(promise, y, resolve, reject);
                }, err => {
                    reject(err);
                })
            } else {
                // 此處then是普通對象,則直接調用下個then的成功態函數並被當作參數輸出
                resolve(res);
            }
        } catch (error) {
            reject(error);
        }
    } else {
        // res 是數值
        resolve(res);
    }
}
then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            let res = onFuiFiled(this.value);
            analysisPromise(newpro, res, resolve, reject);
        });
    }
    ...
    return newpro;
}
複製代碼

analysisPromise函數就是用來解析Promise中then函數的返回值的,在調用then函數中的狀態函數返回結果值時都必需要進行處理。

Promise中的then函數是異步

如今,咱們再來看Promise的一個例子:

let pro = new Promise((resolve, reject) => {
    resolve(123);
});
pro.then(data => {
    console.log(1);
});
console.log(2);
複製代碼

輸出的結果是 2 1

so,這裏先輸出的是2,而then函數中的狀態函數後輸出1,說明同步代碼先於then函數的異步代碼執行

那這部分的代碼咱們該如何來實現呢?

採用setTimeout函數,給then函數中的每一個匿名函數都加上setTimeout函數代碼,就好比這樣:

then(onFuiFiled, onRejected) {
    let newpro;
    if (this.status === "fulfilled") {
        newpro = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let res = onFuiFiled(this.value);
                    analysisPromise(newpro, res, resolve, reject);
                } catch (error) {
                    reject(error);
                }   
            });
        });
    }
    ...
    return newpro;
}
複製代碼

好了,關於Promise的大體實現就先分析到這裏,但願對你們有幫助,文章中若有錯誤,歡迎指正

文章主要參考一下具體學習資源

  • http://es6.ruanyifeng.com/
  • http://liubin.org/promises-book/#es6-promises
相關文章
相關標籤/搜索