學習Promise基礎及手寫Promise

對於Promise,相信大多數人都已經瞭解而且能夠熟練的使用它的各類方法,可是追究它的底層原理,可能並不清楚,這篇文章是本身在通過一段時間的學習後寫出來的,裏面包含了本身對Promise的理解,而且按照步驟一步一步的手寫了Promise以及then方法,但願這篇文章能夠幫助到大家。 編程

學習Promise

Promise的含義

Promise 是異步編程的一種解決方案,ES6將其寫進了語言標準。所謂Promise就是一個容器,裏面保存着將來纔會結束的事件(一般是一個異步操做)的結果promise

Promise對象有如下兩個特色:瀏覽器

  • 對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled/resolved(已成功)、rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
  • 一旦狀態改變,就不會再改變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。

Promise的優勢:bash

能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。異步

基本用法

ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。async

const promise = new Promise((resolve,reject)=>{
//此處執行一些異步操做(調用後臺API,定時器等)
 if(/*異步操做成功*/){
     resolve(value);
 }else{
     reject(error)
 }
}) 
//其中兩個函數的參數值分別爲成功和失敗後想要傳遞的結果
複製代碼

Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由 JavaScript 引擎提供,不用本身部署。異步編程

resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject函數的做用是,將Promise對象的狀態從「未完成」變爲「失敗」(即從 pending變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。函數

then方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受Promise對象傳出的值做爲參數。post

promise.then(res=>{
    //對於成功回調接受的數據作處理
},err=>{
    //對於失敗的回調數據作處理
})
複製代碼

注:Promise新建後就會當即執行。學習

Promise.prototype.then() Promise實例具備then方法,也就是說,then方法是定義在原型對象上Promise.prototype上的,它的做用是爲 Promise實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。第一個回調函數完成之後,會將返回結果做爲參數,春如第二個回調函數。

採用鏈式的 then,能夠指定一組按照次序調用的回調函數。(ES7中的async/await)也能夠實現鏈式調用,除此以外,Promiseall方法能夠實現並行執行。

瞭解了基礎的 Promisethen以後,咱們即可以本身建立一個 Promise

手寫Promise

首先寫 Promise構造函數,由以上Promise的使用可知,其參數爲一個函數,又被稱爲執行器函數(executor),而且執行器函數會被當即調用,執行器函數也會接收兩個參數,且這兩個參數均爲函數。

function Promise(executor) {
    executor(resolve, reject);
}
複製代碼

Promise最重要的方法就是then方法,所以爲了可以讓實例調用這個方法,咱們必須將這個方法寫在其原型鏈上,而且它接受兩個參數,一個爲成功的回調,一個爲失敗得回調。

Promise.prototype.then=function(onResolved,onRejected){
    
}
複製代碼

以後繼續寫Promise函數,由於new出來的實例具備默認的狀態pending,以後經過執行器executor執行 resolvereject兩個函數來修改狀態。

function Promise(executor) {
    let self=this;                   //保留this。防止後面方法出現this只想不明的問題
    self.status='pending';           //promise的默認狀態是pending

    function resolve(){
        self.status='resolved';      //成功函數將其狀態修改成resolved
    }
    function reject(){
        self.status='rejected';      //失敗函數將其函數修改成rejected
    }
    executor(resolve, reject);
}
複製代碼

爲了保證 Promise實例狀態一旦變動不能再次改變,須要進行判斷

function Promise(executor) {
    let self = this;                       //保留this。防止後面方法出現this只想不明的問題
    self.status = 'pending';               //promise的默認狀態是pending
    self.success = undefined;              //保存成功回調傳遞的值
    self.error = undefined;                //保存失敗回調傳遞的值

    function resolve() {
        if (self.status === 'pending') {
            self.status = 'resolved';      //成功函數將其狀態修改成resolved
        }
    }
    function reject() {
        if (self.status === 'pending') {
            self.status = 'rejected';      //失敗函數將其函數修改成rejected
        }
    }
    executor(resolve, reject);
}
複製代碼

以後須要將調用以後的成功或失敗的結果保存起來

function Promise(executor) {
    let self = this;                       //保留this。防止後面方法出現this只想不明的問題
    self.status = 'pending';               //promise的默認狀態是pending
    self.success = undefined;              //保存成功回調傳遞的值
    self.error = undefined;                //保存失敗回調傳遞的值

    function resolve(success) {
        if (self.status === 'pending') {
            self.status = 'resolved';      //成功函數將其狀態修改成resolved
            self.success=success;          //將成功的值保存起來
        }
    }
    function reject(error) {
        if (self.status === 'pending') {
            self.status = 'rejected';      //失敗函數將其函數修改成rejected
            self.error=error;              //將失敗的值保存起來
        }
    }
    executor(resolve, reject);
}
複製代碼

在這裏舉一個實際的例子(Express使用Promise保存的須要返回的值)

注意:該例子涉及到了異步函數處理,鏈式調用,放在此處只是爲了說明上面的概念。

當執行器調用 resolve函數後,then中的第一個參數函數(成功回調)會執行,並將保存的值傳遞給then中的第一個函數做爲參數,同時當執行器調用 reject函數後,then中的第二個參數函數(失敗回調)會執行,並將保存的值傳遞給then中的第二個函數做爲參數。

Promise.prototype.then = function (onResolved, onRejected) {
    let self = this;
    if (self.status === 'resolved'); {
        onResolved(self.success);           //將resolve函數保留的成功值傳遞做爲參數
    }
    if (self.status === 'rejected') {
        onRejected(self.error);              //將reject函數保留的失敗值傳遞做爲參數
    }
}
複製代碼

對應於上面的例子,舉出其then的使用

到此爲止Promise的簡單結構已經基本完成,簡單測試

let promise = new Promise((resolve, reject) => {
    console.log('start');
    resolve('success data');
})

promise.then(res => {
    console.log("res", res);
}, err => {
    console.log("err", err);
})

複製代碼

測試結果

start
res success data
複製代碼

以上步驟只是實現了同步處理,接下來實現異步處理以及實現一個實例屢次調用then方法(不是鏈式調用)

由於 js是單線程的,簡單理解瀏覽器端的事件循環即爲先執行同步任務,後執行異步任務。同步任務是存放在調用棧中的,主線程會先執行同步任務,當調用棧中的同步任務全都執行完畢且主線程爲空時,主線程會去任務隊列中查找是否有已經註冊的異步任務的回調函數,有則執行,無則等待。任務隊列中的異步任務又分爲微任務和宏任務,這二者也有相應的執行順序。詳細介紹能夠等下篇文章

言歸正傳,實現異步處理及屢次調用

若是Promise處理的爲一個異步函數,那麼當then的時候,執行器函數中的參數會被放到異步任務隊列中,即爲此時Promise的實例仍爲默認狀態pending,沒有改變,那麼咱們此時並不知道要去執行then中的成功回調函數仍是失敗回調函數,在不知道哪一個回調函數會被執行的狀況下,就須要把這兩個回調函數保存起來,等到時機成熟,肯定哪一個函數的時候,再拿出來調用。

function Promise(executor) {
    let self = this; //保留this。防止後面方法出現this只想不明的問題
    self.status = 'pending'; //promise的默認狀態是pending
    self.success = undefined; //保存成功回調傳遞的值
    self.error = undefined; //保存失敗回調傳遞的值

    self.onSuccessCallbacks = []; //存放成功的回調
    self.onErrorCallbacks = []; //存放失敗的回調

    function resolve(success) {
        if (self.status === 'pending') {
            self.status = 'resolved'; //成功函數將其狀態修改成resolved
            self.success = success; //將成功的值保存起來
            self.onSuccessCallbacks.forEach(element => {
                element();
            });
        }
    }

    function reject(error) {
        if (self.status === 'pending') {
            self.status = 'rejected'; //失敗函數將其函數修改成rejected
            self.error = error; //將失敗的值保存起來
            self.onErrorCallbacks.forEach(element => {
                element();
            })
        }
    }
    executor(resolve, reject);
}


Promise.prototype.then = function (onResolved, onRejected) {
    let self = this;
    if (self.status === 'pending') {
        self.onSuccessCallbacks.push(() => {
            onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
        })
        self.onErrorCallbacks.push(() => {
            onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
        })
    }
    if (self.status === 'resolved') {
        onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
    }
    if (self.status === 'rejected') {
        onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
    }
}

複製代碼

測試用例

let promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve('success data')
    }, 2000)
})

promise.then(res => {
    console.log("success:", res);
}, err => {
    console.log("error:", err);
})
promise.then(res => {
    console.log("success:", res);
}, err => {
    console.log("error:", err);
})
複製代碼

測試結果爲2秒後出現結果

success: success data
success: success data
複製代碼

繼續進行嘗試,若是讓Promise拋出一個錯誤如何處理

let promise = new Promise((resolve, reject) => {
    throw new error("一個錯誤");
})

promise.then(res => {
    console.log("success:", res);
}, err => {
    console.log("error:", err);
})
複製代碼

結果:

解決該問題

try {
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
複製代碼

再次嘗試,查看結果

修改結果則爲直接對 executor函數進行異常處理,若是出錯了就直接進入 reject方法。

完成上面的一系列完善以後,最後咱們實現Promise的鏈式調用。

Promise實現鏈式調用就是經過then方法返回一個新的Promise

若是返回的是一個Promise函數,那麼會等待這個Promise執行完成以後再返回給下一次的thenPromise若是成功,就會走下一次then的成功,若是失敗就會走下一次then的失敗。

注意:then方法中返回的回調函數不能是本身自己,若是真的這樣寫,那麼函數執行到裏面時會等待promise的結果,這樣一層層的狀態等待就會造成回調地獄

接下來一步步分析(只須要改進then函數便可)

then函數中嵌套new Promise

以後主要爲resolvePromise函數,對x進行判斷,作出相應的操做:

到此基本功能已經完成,如下爲源碼以及測試例子及結果

源碼:

//Promise函數
function Promise(executor) {
    let self = this; //保留this。防止後面方法出現this只想不明的問題
    self.status = 'pending'; //promise的默認狀態是pending
    self.success = undefined; //保存成功回調傳遞的值
    self.error = undefined; //保存失敗回調傳遞的值

    self.onSuccessCallbacks = []; //存放成功的回調
    self.onErrorCallbacks = []; //存放失敗的回調

    function resolve(success) {
        if (self.status === 'pending') {
            self.status = 'resolved'; //成功函數將其狀態修改成resolved
            self.success = success; //將成功的值保存起來
            self.onSuccessCallbacks.forEach(element => {
                element();
            });
        }
    }

    function reject(error) {
        if (self.status === 'pending') {
            self.status = 'rejected'; //失敗函數將其函數修改成rejected
            self.error = error; //將失敗的值保存起來
            self.onErrorCallbacks.forEach(element => {
                element();
            })
        }
    }
    try {
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}

//then函數
Promise.prototype.then = function (onResolved, onRejected) {
    let self = this;
    let promiseAgain = new Promise((resolve, reject) => {
        if (self.status === 'pending') {
            self.onSuccessCallbacks.push(() => {
                let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
                resolvePromise(promiseAgain, x, resolve, reject);
            })
            self.onErrorCallbacks.push(() => {
                let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
                resolvePromise(promiseAgain, x, resolve, reject);
            })
        }
        if (self.status === 'resolved') {
            let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
            resolvePromise(promiseAgain, x, resolve, reject);
        }
        if (self.status === 'rejected') {
            let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
            resolvePromise(promiseAgain, x, resolve, reject);
        }
    })
    return promiseAgain;
}
//resolvePromise函數
function resolvePromise(promiseAgain, x, resolve, reject) {
    if (promiseAgain === x) {
        return reject(new TypeError("循環調用"));
    }
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, (y) => {
                    resolvePromise(promiseAgain, y, resolve, reject);
                }, (e) => {
                    reject(e);
                })
            } else {
                resolve(x);
            }
        } catch (error) {
            reject(error);
        }
    } else {
        resolve(x);
    }
}

module.exports = Promise;
複製代碼

測試示例:

let Promise = require('./Promise');


let promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve('success data')
    }, 2000)
})

promise.then(res => {
        console.log("第一次調用", res);
        return res;
    }, err => {
        console.log("error:", err);
    })
    .then(res => {
        console.log("第二次調用",res);
        return res
    }, err => {
        console.log("err", err);
    })
    .then(res => {
        console.log("第三次調用",res);
    }, err => {
        console.log("err", err);
    })
複製代碼

測試結果:

第一次調用 success data
第二次調用 success data
第三次調用 success data
複製代碼

使用過Promise,咱們隨口而出即爲Promise爲異步函數,其實Promise在實例化(new的過程)的時候是同步的,而then中註冊的回調纔是異步執行的。

let Promise = require('./Promise');


let promise = new Promise((resolve, reject) => {
    console.log("其次會被執行");
    resolve("success data");
})

promise.then(res => {
        console.log("第一次調用", res);
        // return res;
    }, err => {
        console.log("error:", err);
    })

console.log("首先會被執行");
複製代碼

執行結果:

其次會被執行
第一次調用 success data
首先會被執行
複製代碼

最終代碼:

//Promise函數
function Promise(executor) {
    let self = this; //保留this。防止後面方法出現this只想不明的問題
    self.status = 'pending'; //promise的默認狀態是pending
    self.success = undefined; //保存成功回調傳遞的值
    self.error = undefined; //保存失敗回調傳遞的值

    self.onSuccessCallbacks = []; //存放成功的回調
    self.onErrorCallbacks = []; //存放失敗的回調

    function resolve(success) {
        if (self.status === 'pending') {
            self.status = 'resolved'; //成功函數將其狀態修改成resolved
            self.success = success; //將成功的值保存起來
            self.onSuccessCallbacks.forEach(element => {
                element();
            });
        }
    }

    function reject(error) {
        if (self.status === 'pending') {
            self.status = 'rejected'; //失敗函數將其函數修改成rejected
            self.error = error; //將失敗的值保存起來
            self.onErrorCallbacks.forEach(element => {
                element();
            })
        }
    }
    try {
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}

//then函數
Promise.prototype.then = function (onResolved, onRejected) {
    onResolved = typeof onResolved == 'function' ? onResolved : val => val;
    onRejected = typeof onRejected == 'function' ? onRejected : err => {
        throw err;
    }
    let self = this;
    let promiseAgain = new Promise((resolve, reject) => {
        if (self.status === 'pending') {
            self.onSuccessCallbacks.push(() => {
                try {
                    let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
                    resolvePromise(promiseAgain, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            })
            self.onErrorCallbacks.push(() => {
                try {
                    let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
                    resolvePromise(promiseAgain, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            })
        }
        if (self.status === 'resolved') {
            try {
                let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
                resolvePromise(promiseAgain, x, resolve, reject);
            } catch (e) {
                reject(e)
            }
        }
        if (self.status === 'rejected') {
            try {
                let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
                resolvePromise(promiseAgain, x, resolve, reject);
            } catch (e) {
                reject(e)
            }
        }
    })
    return promiseAgain;
}
//resolvePromise函數
function resolvePromise(promiseAgain, x, resolve, reject) {
    if (promiseAgain === x) {
        return reject(new TypeError("循環調用"));
    }
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, (y) => {
                    resolvePromise(promiseAgain, y, resolve, reject);
                }, (e) => {
                    reject(e);
                })
            } else {
                resolve(x);
            }
        } catch (error) {
            reject(error);
        }
    } else {
        resolve(x);
    }
}

module.exports = Promise;
複製代碼

測試實例:

let Promise = require('./Promise');


let promise = new Promise((resolve, reject) => {
    setTimeout(function () {
        resolve('success data')
    }, 0)
})

promise.then(res => {
        console.log("第一次調用", res);
        return res;
    }, err => {
        console.log("error:", err);
    })
    .then(res => {
        console.log("第二次調用",res);
        return res
    }, err => {
        console.log("err", err);
    })
    .then(res => {
        console.log("第三次調用",res);
    }, err => {
        console.log("err", err);
    })

console.log("首先會被執行");
複製代碼

測試結果:

首先會被執行
第一次調用 success data
第二次調用 success data
第三次調用 success data
複製代碼

此篇文章只是本身在瞭解Promise源碼以後,結合本身的理解寫下的,但願這篇文章能夠有所幫助。

在最後推薦一下各位讀者在瞭解Promise的時候,能夠了解一下瀏覽器的事件循環,這對於一段混雜着setTimeOutPromise的輸出順序有好大的幫助,這裏推薦一篇文章瀏覽器端事件循環,以後逼着可能也會寫一下瀏覽器的事件循環文章。

相關文章
相關標籤/搜索