【小小前端】手寫一個很簡單的異步編程解決方案Promise及其鏈式調用

前端異步一直是個熱門話題,做爲新一代異步編程解決方案的Promise,相比傳統異步編程,不只解決了恐怖的回調地獄,在寫法上也是至關方便簡潔。如此牛*的功能讓人不由地想一探究竟,在各大網絡公司面試中,手寫Promise也是常見的筆試題了,就讓咱們一塊兒來簡單探討一下Promise的實現吧!javascript

引子


什麼是Promise?

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。前端

Promise簡單用法

// 定義一個Promise對象
let promise = new Promise((resolve,reject) => {
    true&&resolve(true) || reject(false)
});

promise.then(res=>{
    console.log(res)
}).catch(err=>{
    throw err
})
複製代碼

如此一個低級簡單的Promise就完成了。java

正文


思考一下,須要實現哪些功能

  1. 定義一個Promise
  2. 在Promise內部,須要一個構造函數(constructor),構造函數有兩個參數:resolvereject,構造函數內部須要一個狀態變量state(pending、resolved、rejected),一個異步成功的回參res
  3. 既然是Promise那必須得有then方法,有兩個參數分別是onFulfilledonRejected
  4. 有了then,天然有catchfinally等一系列。。。。。。

聲明一個Promise

既然是手寫,那麼咱們直接將原來的Promise對象覆蓋,這裏採用ES6的class寫法。面試

聲明構造函數

  • Promise存在三個狀態(state)pending、fulfilled、rejected
  • pending(等待態)爲初始態,並能夠轉化爲fulfilled(成功態)和rejected(失敗態)
  • 無論成功仍是失敗,最終獲得的res或者reason都沒法改變
class Promise {
    // 構造函數
    constructor(executor){
        // 狀態控制state,默認值值爲pending、成功resolved、失敗rejected
        this.state = 'pending';
        // 成功返回值
        this.res = '';
        // 失敗返回緣由
        this.reason = '';
        // resolve方法
        let resolve = (res) => {
            // 若是狀態state爲pending,將狀態變爲resolved,並將返回值賦給res
            this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
        };
        
        let reject = (reason) => {
            // 若是狀態state爲pending,將狀態變爲rejected,並返回錯誤緣由
            this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
        };
        
        // 若是executor報錯的話,直接執行reject
        try {
            executor(resolve,reject)
        } catch (error) {
            reject(error)
        }
    }
}
複製代碼

實現then方法

then(onFulfilled,onRejected){
    // state爲resolved,執行onFulfilled
    this.state === 'resolved' && (()=>{
        onFulfilled(this.res)
    })();
    // state爲rejected,執行onRejected
    this.state === 'rejected' && (()=>{
        onRjected(this.reason)
    })();
}
複製代碼

牛刀小試

此時,一個最基本最簡單最低級的Promise就完成了,讓咱們先測試一下結果,隨機一個數,若是大於0.5則成功,若是小於則失敗。編程

let promise = new Promise((resolve,reject)=>{
    let random = Math.random();
    if(random>0.5){
        resolve(random)
    } else{
        reject(random)
    }
});

promise.then(res=>{
    console.log('成功:' + res)
},err=>{
    console.log('失敗:' + err)
})
複製代碼

刷新控制檯:數組

看起來沒什麼毛病,不過若是你認爲這就結束了?promise

仍是這該死的異步

有人問,Promise不就是解決異步的,怎麼又說到異步,這裏要說的異步就是指定時器了,好比setTimeout,爲何這樣說,這就涉及到js的事件循環機制(eventLoop)了,這裏暫且不提。網絡

先看一個例子:dom

let promise = new Promise((resolve,reject)=>{
    let random = Math.random();
    if(random>0.5){
        setTimeout(() => {
            resolve(random)
        }, 1000);
    } else{
        setTimeout(() => {
            reject(random)
        }, 1000);
    }
});

promise.then(res=>{
    console.log('成功:' + res)
},err=>{
    console.log('失敗:' + err)
})

複製代碼

此時,無論怎麼刷新,控制檯就是不打印結果,無論正確仍是錯誤,這是爲何呢?異步

首先咱們定義了一個Promise對象的實例,在實例中定義了一個定時器,一秒後返回,當運行到定時器以後,定時器掛起,繼續執行下面的promise.then,而此時由於定時器的緣故,無論是resolve仍是reject都沒有運行,也就是說state的狀態任然沒有改變,因此在then方法中,沒有對應的項,若是再次執行then方法,同理依然沒法運行,那麼問題來了,如何解決由於定時器致使的狀態未變化和屢次執行then方法呢?

改造構造函數

首先爲了屢次執行then方法,咱們定義兩個空數組,分別存放成功或者失敗方法,而後在then方法中,若是狀態依然是pending,則將成功或者失敗的函數存入對應的數組,當定時器時間過了以後,在resolve或者reject中循環對應數組並執行函數。

constructor(executor){
    // 原先代碼不變
    // ......
    // 添加數組
    // 成功列表
    this.resolvedList = [];
    // 失敗列表
    this.rejectedList = [];
    // 改造一下resolve和reject
    let resolve = (res) => {
        this.state === 'pending' && (this.state = 'resolved') && (this.res = res);
        this.state === 'resolved' && this.resolvedList.forEach(fn => {
            fn();
        })
    };
    // 失敗
    let reject = (reason) => {
        this.state === 'pending' && (this.state = 'rejected') && (this.reason = reason);
        this.state === 'rejected' && this.rejectedList.forEach(fn => {
            fn();
        })
    };
}
複製代碼

改造then方法

因爲定時器的緣由,形成先執行then,那麼在then中將對應函數存起:

then(onFulfilled,onRejected){
    this.state === 'pending' && (() =>{
        this.resolvedList.push(()=>{
            console.log('resolvedList.push')
            onFulfilled(this.res);
        });
        this.rejectedList.push(()=>{
            onRejected(this.reason)
        });
        return
    })()
    // state爲resolved,執行onFulfilled
    this.state === 'resolved' && (()=>{
        onFulfilled(this.res)
    })();
    // state爲rejected,執行onRejected
    this.state === 'rejected' && (()=>{
        onRjected(this.reason)
    })();
}
複製代碼

調用then方法

promise.then(
    res => {
        console.log("成功1:" + res);
    },
    err => {
        console.log("失敗1:" + err);
    }
);
promise.then(
    res => {
        console.log("成功2:" + res);
    },
    err => {
        console.log("失敗2:" + err);
    }
);
複製代碼

刷新控制檯:

此時控制檯已經能夠正常打印。

then方法的鏈式調用

爲了解決回調地獄,在Promise中,咱們是這樣的寫法:new Promise().then().then(),道理其實很簡單,第一個then方法返回的值也是一個Promise對象,那麼就能繼續使用then方法。

當咱們在第一個then中return了一個參數(參數未知,需判斷)。這個return出來的新的promise就是onFulfilled()或onRejected()的值:

  • 返回值是普通值如:數值、字符串等,直接將其做爲callBackPromise成功的結果
  • 若是是一個Promise,則取其結果做爲callBackPromise成功的結果

一個判斷返回值的函數callBackPromise

  • res 不能是null
  • 聲明瞭then,若是取then報錯,則走reject()
function callBackFunction(callBackPromise,res, resolve, reject){
    // 判斷callBackPromise === res ,若是等於會形成死循環
    if(callBackPromise === res){
        return reject(new TypeError('注意死循環了!'));
    }
    // 防止重複調用
    let reCall;
    if(res !== null && (typeof res === 'Object' || typeof res === 'function')){
        try {
            // 鏈式,調用上一次結果的then
            let then = res.then;
            typeof then === 'function' && then.call(res, nextRes => {
                if(reCall){
                    return 
                }
                reCall = true;
                callBackPromise(callBackPromise,nextRes,resolve,reject)
            },err => {
                if(reCall)
                return;
                reject(err);
            });
            resolve(res)
        }catch(e){
            if(reCall)
            return;
            reject(e)
        }
    }else{
        reject(res)
        // resolve(res)
    }
}
複製代碼

繼續改造then方法

then(onFulfilled,onRejected){
    // 注意此處用到的var,若是用let會形成callBackFunction報錯,緣由是let沒有變量提高
    var callBackPromise = new Promise((resolve,reject) => {
        this.state === 'pending' && (() =>{
            this.resolvedList.push(()=>{
                let res = onFulfilled(this.res);
                callBackFunction(callBackPromise,res,resolve,reject)
            });
            this.rejectedList.push(()=>{
                let res = onRejected(this.reason)
                callBackFunction(callBackPromise,res,resolve,reject)
            });
            return
        })()
        this.state === 'resolved' && (()=>{
            let res = onFulfilled(this.res);
            callBackFunction(callBackPromise,res,resolve,reject)
        })();
        this.state === 'rejected' && (()=>{
            let res = onRejected(this.reason);
            callBackFunction(callBackPromise,res,resolve,reject)
        })();
    })
    return callBackPromise;
}
複製代碼

測試一下,若是打印爲空多刷新幾回,由於沒有實現catch方法:

promise.then(res => {
    console.log('第一次回調',res)
    return '第一次返回'
})
.then(res2=>{
    console.log('第二次回調',res2)
    return '第二次回調'
})
.then(res3=>{
    console.log('第三次回調',res3)
})
複製代碼

能夠看到此時已經能夠進行鏈式操做。

後記

到此,一個基本的鏈式調用已經完成,然而Promise的博大精深遠不及此,諸如onFulfilled,onRejected的可選性,catch、finally、all等方法的實現都尚未完成,須要不斷完善,若是上述代碼中有錯誤的還請各位大佬指出,輕噴。

參考連接

相關文章
相關標籤/搜索