成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

只會用就out了,手寫一個符合規(guī)范的Promise

muzhuyu / 1641人閱讀

摘要:傳入的回調(diào)函數(shù)也不是一個函數(shù)類型,那怎么辦規(guī)范中說忽略它就好了。因此需要判斷一下回調(diào)函數(shù)的類型,如果明確是個函數(shù)再執(zhí)行它。

Promise是什么

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理。

Promise是處理異步編碼的一個解決方案,在Promise出現(xiàn)以前,異步代碼的編寫都是通過回調(diào)函數(shù)來處理的,回調(diào)函數(shù)本身沒有任何問題,只是當(dāng)多次異步回調(diào)有邏輯關(guān)系時就會變得復(fù)雜:

const fs = require("fs");
fs.readFile("1.txt", (err,data) => {
    fs.readFile("2.txt", (err,data) => {
        fs.readFile("3.txt", (err,data) => {
            //可能還有后續(xù)代碼
        });
    });
});

上面讀取了3個文件,它們是層層遞進的關(guān)系,可以看到多個異步代碼套在一起不是縱向發(fā)展的,而是橫向,不論是從語法上還是從排錯上都不好,于是Promise的出現(xiàn)可以解決這一痛點。

上述代碼如果改寫成Promise版是這樣:

const util = require("util");
const fs = require("fs");
const readFile = util.promisify(fs.readFile);

readFile("1.txt")
    .then(data => {
        return readFile("2.txt");
    }).then(data => {
        return readFile("3.txt");
    }).then(data => {
        //...
    });

可以看到,代碼是從上至下縱向發(fā)展了,更加符合人們的邏輯。

下面手寫一個Promise,按照Promises/A+規(guī)范,可以參照規(guī)范原文:
Promises/A+規(guī)范

手寫實現(xiàn)Promise是一道前端經(jīng)典的面試題,比如美團的面試就是必考題,Promise的邏輯還是比較復(fù)雜的,考慮的邏輯也比較多,下面總結(jié)手寫Promise的關(guān)鍵點,和怎樣使用代碼來實現(xiàn)它。

Promise代碼基本結(jié)構(gòu)

實例化Promise對象時傳入一個函數(shù)作為執(zhí)行器,有兩個參數(shù)(resolve和reject)分別將結(jié)果變?yōu)槌晒B(tài)和失敗態(tài)。我們可以寫出基本結(jié)構(gòu)

function Promise(executor) {
    this.state = "pending"; //狀態(tài)
    this.value = undefined; //成功結(jié)果
    this.reason = undefined; //失敗原因

    function resolve(value) {
        
    }

    function reject(reason) {

    }
}

module.exports = Promise;

其中state屬性保存了Promise對象的狀態(tài),規(guī)范中指明,一個Promise對象只有三種狀態(tài):等待態(tài)(pending)成功態(tài)(resolved)和失敗態(tài)(rejected)
當(dāng)一個Promise對象執(zhí)行成功了要有一個結(jié)果,它使用value屬性保存;也有可能由于某種原因失敗了,這個失敗原因放在reason屬性中保存。

then方法定義在原型上

每一個Promise實例都有一個then方法,它用來處理異步返回的結(jié)果,它是定義在原型上的方法,我們先寫一個空方法做好準(zhǔn)備:

Promise.prototype.then = function (onFulfilled, onRejected) {
};
當(dāng)實例化Promise時會立即執(zhí)行

當(dāng)我們自己實例化一個Promise時,其執(zhí)行器函數(shù)(executor)會立即執(zhí)行,這是一定的:

let p = new Promise((resolve, reject) => {
    console.log("執(zhí)行了");
});

運行結(jié)果:

執(zhí)行了

因此,當(dāng)實例化Promise時,構(gòu)造函數(shù)中就要馬上調(diào)用傳入的executor函數(shù)執(zhí)行

function Promise(executor) {
    var _this = this;
    this.state = "pending";
    this.value = undefined;
    this.reason = undefined;

    executor(resolve, reject); //馬上執(zhí)行
    
    function resolve(value) {}
    function reject(reason) {}
}
已經(jīng)是成功態(tài)或是失敗態(tài)不可再更新狀態(tài)

規(guī)范中規(guī)定,當(dāng)Promise對象已經(jīng)由pending狀態(tài)改變?yōu)榱顺晒B(tài)(resolved)或是失敗態(tài)(rejected)就不能再次更改狀態(tài)了。因此我們在更新狀態(tài)時要判斷,如果當(dāng)前狀態(tài)是pending(等待態(tài))才可更新:

    function resolve(value) {
        //當(dāng)狀態(tài)為pending時再做更新
        if (_this.state === "pending") {
            _this.value = value;//保存成功結(jié)果
            _this.state = "resolved";
        }

    }

    function reject(reason) {
    //當(dāng)狀態(tài)為pending時再做更新
        if (_this.state === "pending") {
            _this.reason = reason;//保存失敗原因
            _this.state = "rejected";
        }
    }

以上可以看到,在resolve和reject函數(shù)中分別加入了判斷,只有當(dāng)前狀態(tài)是pending才可進行操作,同時將成功的結(jié)果和失敗的原因都保存到對應(yīng)的屬性上。之后將state屬性置為更新后的狀態(tài)。

then方法的基本實現(xiàn)

當(dāng)Promise的狀態(tài)發(fā)生了改變,不論是成功或是失敗都會調(diào)用then方法,所以,then方法的實現(xiàn)也很簡單,根據(jù)state狀態(tài)來調(diào)用不同的回調(diào)函數(shù)即可:

Promise.prototype.then = function (onFulfilled, onRejected) {
    if (this.state === "resolved") {
        //判斷參數(shù)類型,是函數(shù)執(zhí)行之
        if (typeof onFulfilled === "function") {
            onFulfilled(this.value);
        }

    }
    if (this.state === "rejected") {
        if (typeof onRejected === "function") {
            onRejected(this.reason);
        }
    }
};

需要一點注意,規(guī)范中說明了,onFulfilled 和 onRejected 都是可選參數(shù),也就是說可以傳也可以不傳。傳入的回調(diào)函數(shù)也不是一個函數(shù)類型,那怎么辦?規(guī)范中說忽略它就好了。因此需要判斷一下回調(diào)函數(shù)的類型,如果明確是個函數(shù)再執(zhí)行它。

讓Promise支持異步

代碼寫到這里似乎基本功能都實現(xiàn)了,可是還有一個很大的問題,目前此Promise還不支持異步代碼,如果Promise中封裝的是異步操作,then方法無能為力:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    },500);
});

p.then(data => console.log(data)); //沒有任何結(jié)果

運行以上代碼發(fā)現(xiàn)沒有任何結(jié)果,本意是等500毫秒后執(zhí)行then方法,哪里有問題呢?原因是setTimeout函數(shù)使得resolve是異步執(zhí)行的,有延遲,當(dāng)調(diào)用then方法的時候,此時此刻的狀態(tài)還是等待態(tài)(pending),因此then方法即沒有調(diào)用onFulfilled也沒有調(diào)用onRejected。

這個問題如何解決?我們可以參照發(fā)布訂閱模式,在執(zhí)行then方法時如果還在等待態(tài)(pending),就把回調(diào)函數(shù)臨時寄存到一個數(shù)組里,當(dāng)狀態(tài)發(fā)生改變時依次從數(shù)組中取出執(zhí)行就好了,清楚這個思路我們實現(xiàn)它,首先在類上新增兩個Array類型的數(shù)組,用于存放回調(diào)函數(shù):

function Promise(executor) {
    var _this = this;
    this.state = "pending";
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFunc = [];//保存成功回調(diào)
    this.onRejectedFunc = [];//保存失敗回調(diào)
    //其它代碼略...
}

這樣當(dāng)then方法執(zhí)行時,若狀態(tài)還在等待態(tài)(pending),將回調(diào)函數(shù)依次放入數(shù)組中:

Promise.prototype.then = function (onFulfilled, onRejected) {
    //等待態(tài),此時異步代碼還沒有走完
    if (this.state === "pending") {
        if (typeof onFulfilled === "function") {
            this.onFulfilledFunc.push(onFulfilled);//保存回調(diào)
        }
        if (typeof onRejected === "function") {
            this.onRejectedFunc.push(onRejected);//保存回調(diào)
        }
    }
    //其它代碼略...
};

寄存好了回調(diào),接下來就是當(dāng)狀態(tài)改變時執(zhí)行就好了:

    function resolve(value) {
        if (_this.state === "pending") {
            _this.value = value;
            //依次執(zhí)行成功回調(diào)
            _this.onFulfilledFunc.forEach(fn => fn(value));
            _this.state = "resolved";
        }

    }

    function reject(reason) {
        if (_this.state === "pending") {
            _this.reason = reason;
            //依次執(zhí)行失敗回調(diào)
            _this.onRejectedFunc.forEach(fn => fn(reason));
            _this.state = "rejected";
        }
    }

至此,Promise已經(jīng)支持了異步操作,setTimeout延遲后也可正確執(zhí)行then方法返回結(jié)果。

鏈?zhǔn)秸{(diào)用

Promise處理異步代碼最強大的地方就是支持鏈?zhǔn)秸{(diào)用,這塊也是最復(fù)雜的,我們先梳理一下規(guī)范中是怎么定義的:

每個then方法都返回一個新的Promise對象(原理的核心

如果then方法中顯示地返回了一個Promise對象就以此對象為準(zhǔn),返回它的結(jié)果

如果then方法中返回的是一個普通值(如Number、String等)就使用此值包裝成一個新的Promise對象返回。

如果then方法中沒有return語句,就視為返回一個用Undefined包裝的Promise對象

若then方法中出現(xiàn)異常,則調(diào)用失敗態(tài)方法(reject)跳轉(zhuǎn)到下一個then的onRejected

如果then方法沒有傳入任何回調(diào),則繼續(xù)向下傳遞(值的傳遞特性)。

規(guī)范中說的很抽像,我們可以把不好理解的點使用代碼演示一下。

其中第3項,如果返回是個普通值就使用它包裝成Promise,我們用代碼來演示:

let p =new Promise((resolve,reject)=>{
    resolve(1);
});

p.then(data=>{
    return 2; //返回一個普通值
}).then(data=>{
    console.log(data); //輸出2
});

可見,當(dāng)then返回了一個普通的值時,下一個then的成功態(tài)回調(diào)中即可取到上一個then的返回結(jié)果,說明了上一個then正是使用2來包裝成的Promise,這符合規(guī)范中說的。

第4項,如果then方法中沒有return語句,就視為返回一個用Undefined包裝的Promise對象

let p = new Promise((resolve, reject) => {
    resolve(1);
});

p.then(data => {
    //沒有return語句
}).then(data => {
    console.log(data); //undefined
});

可以看到,當(dāng)沒有返回任何值時不會報錯,沒有任何語句時實際上就是return undefined;即將undefined包裝成Promise對象傳給下一個then的成功態(tài)。

第6項,如果then方法沒有傳入任何回調(diào),則繼續(xù)向下傳遞,這是什么意思呢?這就是Promise中值的穿透,還是用代碼演示一下:

let p = new Promise((resolve, reject) => {
    resolve(1);
});

p.then(data => 2)
.then()
.then()
.then(data => {
    console.log(data); //2
});

以上代碼,在第一個then方法之后連續(xù)調(diào)用了兩個空的then方法 ,沒有傳入任何回調(diào)函數(shù),也沒有返回值,此時Promise會將值一直向下傳遞,直到你接收處理它,這就是所謂的值的穿透。

現(xiàn)在可以明白鏈?zhǔn)秸{(diào)用的原理,不論是何種情況then方法都會返回一個Promise對象,這樣才會有下個then方法。

搞清楚了這些點,我們就可以動手實現(xiàn)then方法的鏈?zhǔn)秸{(diào)用,一起來完善它:

Promise.prototype.then = function (onFulfilled, onRejected) {
    var promise2 = new Promise((resolve, reject) => {
    //代碼略...
    }
    return promise2;
};

首先,不論何種情況then都返回Promise對象,我們就實例化一個新promise2并返回。

接下來就處理根據(jù)上一個then方法的返回值來生成新Promise對象,由于這塊邏輯較復(fù)雜且有很多處調(diào)用,我們抽離出一個方法來操作,這也是規(guī)范中說明的:

/**
 * 解析then返回值與新Promise對象
 * @param {Object} promise2 新的Promise對象 
 * @param {*} x 上一個then的返回值
 * @param {Function} resolve promise2的resolve
 * @param {Function} reject promise2的reject
 */
function resolvePromise(promise2, x, resolve, reject) {
    //...
}

resolvePromise方法用來封裝鏈?zhǔn)秸{(diào)用產(chǎn)生的結(jié)果,下面我們分別一個個情況的寫出它的邏輯,首先規(guī)范中說明,如果promise2x 指向同一對象,就使用TypeError作為原因轉(zhuǎn)為失敗。原文如下:

If promise and x refer to the same object, reject promise with a TypeError as the reason.

這是什么意思?其實就是循環(huán)引用,當(dāng)then的返回值與新生成的Promise對象為同一個(引用地址相同),則會拋出TypeError錯誤:

let promise2 = p.then(data => {
    return promise2;
});

運行結(jié)果:

TypeError: Chaining cycle detected for promise #

很顯然,如果返回了自己的Promise對象,狀態(tài)永遠為等待態(tài)(pending),再也無法成為resolved或是rejected,程序會死掉,因此首先要處理它:

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        reject(new TypeError("Promise發(fā)生了循環(huán)引用"));
    }
}

接下來就是分各種情況處理。當(dāng)x就是一個Promise,那么就執(zhí)行它,成功即成功,失敗即失敗。若x是一個對象或是函數(shù),再進一步處理它,否則就是一個普通值:

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        reject(new TypeError("Promise發(fā)生了循環(huán)引用"));
    }

    if (x !== null && (typeof x === "object" || typeof x === "function")) {
        //可能是個對象或是函數(shù)
    } else {
        //否則是個普通值
        resolve(x);
    }
}

此時規(guī)范中說明,若是個對象,則嘗試將對象上的then方法取出來,此時如果報錯,那就將promise2轉(zhuǎn)為失敗態(tài)。原文:

If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
function resolvePromise(promise2, x, resolve, reject) {
    //代碼略...
    if (x !== null && (typeof x === "object" || typeof x === "function")) {
        //可能是個對象或是函數(shù)
        try {
            let then = x.then;//取出then方法引用
        } catch (e) {
            reject(e);
        }
        
    } else {
        //否則是個普通值
        resolve(x);
    }
}

多說幾句,為什么取對象上的屬性有報錯的可能?Promise有很多實現(xiàn)(bluebird,Q等),Promises/A+只是一個規(guī)范,大家都按此規(guī)范來實現(xiàn)Promise才有可能通用,因此所有出錯的可能都要考慮到,假設(shè)另一個人實現(xiàn)的Promise對象使用Object.defineProperty()惡意的在取值時拋錯,我們可以防止代碼出現(xiàn)Bug。

此時,如果對象中有then,且then是函數(shù)類型,就可以認(rèn)為是一個Promise對象,之后,使用x作為this來調(diào)用then方法。

If then is a function, call it with x as this
//其他代碼略...
if (x !== null && (typeof x === "object" || typeof x === "function")) {
    //可能是個對象或是函數(shù)
    try {
        let then = x.then; 
        if (typeof then === "function") {
            //then是function,那么執(zhí)行Promise
            then.call(x, (y) => {
                resolve(y);
            }, (r) => {
                reject(r);
            });
        } else {
            resolve(x);
        }
    } catch (e) {
        reject(e);
    }

} else {
    //否則是個普通值
    resolve(x);
}

這樣鏈?zhǔn)綄懛ň突就瓿闪?。但是還有一種極端的情況,如果Promise對象轉(zhuǎn)為成功態(tài)或是失敗時傳入的還是一個Promise對象,此時應(yīng)該繼續(xù)執(zhí)行,直到最后的Promise執(zhí)行完。

p.then(data => {
    return new Promise((resolve,reject)=>{
        //resolve傳入的還是Promise
        resolve(new Promise((resolve,reject)=>{
            resolve(2);
        }));
    });
})

此時就要使用遞歸操作了。

規(guī)范中原文如下:

If a promise is resolved with a thenable that participates in a circular thenable chain, such that the recursive nature of [[Resolve]](promise, thenable) eventually causes [[Resolve]](promise, thenable) to be called again, following the above algorithm will lead to infinite recursion. Implementations are encouraged, but not required, to detect such recursion and reject promise with an informative TypeError as the reason.

很簡單,把調(diào)用resolve改寫成遞歸執(zhí)行resolvePromise方法即可,這樣直到解析Promise成一個普通值才會終止,即完成此規(guī)范:

//其他代碼略...
if (x !== null && (typeof x === "object" || typeof x === "function")) {
    //可能是個對象或是函數(shù)
    try {
        let then = x.then; 
        if (typeof then === "function") {
            let y = then.call(x, (y) => {
                //遞歸調(diào)用,傳入y若是Promise對象,繼續(xù)循環(huán)
                resolvePromise(promise2, y, resolve, reject);
            }, (r) => {
                reject(r);
            });
        } else {
            resolve(x);
        }
    } catch (e) {
        reject(e);
    }

} else {
    //是個普通值,最終結(jié)束遞歸
    resolve(x);
}

到此,鏈?zhǔn)秸{(diào)用的代碼已全部完畢。在相應(yīng)的地方調(diào)用resolvePromise方法即可。

最后的最后

其實,寫到此處Promise的真正源碼已經(jīng)寫完了,但是距離100分還差一分,是什么呢?

規(guī)范中說明,Promise的then方法是異步執(zhí)行的。

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

ES6的原生Promise對象已經(jīng)實現(xiàn)了這一點,但是我們自己的代碼是同步執(zhí)行,不相信可以試一下,那么如何將同步代碼變成異步執(zhí)行呢?可以使用setTimeout函數(shù)來模擬一下:

setTimeout(()=>{
    //此處的代碼會異步執(zhí)行
},0);

利用此技巧,將代碼then執(zhí)行處的所有地方使用setTimeout變?yōu)楫惒郊纯?,舉個栗子:

setTimeout(() => {
    try {
        let x = onFulfilled(value);
        resolvePromise(promise2, x, resolve, reject);
    } catch (e) {
        reject(e);
    }
},0);

好了,現(xiàn)在已經(jīng)是滿分的Promise源碼了。

滿分的測試

好不容易寫好的Promise源碼,最終是否真的符合Promises/A+規(guī)范,開源社區(qū)提供了一個包用于測試我們的代碼:promises-aplus-tests

這個包的使用方法不在詳述,此包可以一項項的檢查我們寫的代碼是否合規(guī),如果有任一項不符就會給我們報出來,如果檢查你的代碼一路都是綠色,那恭喜,你的Proimse已經(jīng)合法了,可以上線提供給別人使用了:

872項測試通過!

現(xiàn)在源碼都會寫,終于可以自信的回答面試官的問題了。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97213.html

相關(guān)文章

  • promise/A+規(guī)范翻譯以及手寫實現(xiàn)

    摘要:如果實現(xiàn)滿足所有要求,則實現(xiàn)可能允許。本條款允許使用特定于實現(xiàn)的方法來采用已知一致承諾的狀態(tài)。接下來根據(jù)規(guī)范進行手寫實現(xiàn)注釋偷懶就將對應(yīng)的規(guī)范標(biāo)注出來,其實基本上就是對著規(guī)范實現(xiàn)。 如果要手寫實現(xiàn)promise,那么先看看promise/A+規(guī)范,再來實現(xiàn),將會事半功倍。那么我先翻譯一下Promise/A+規(guī)范中的內(nèi)容。 術(shù)語 1.1 promise 是一個帶有符合此規(guī)范的the...

    LiuZh 評論0 收藏0
  • 一步一步實現(xiàn)一個符合PromiseA+規(guī)范Promise庫(1)

    摘要:今天我們來自己手寫一個符合規(guī)范的庫。是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強大。我們可以看到,其實就是一個構(gòu)造函數(shù)。所以說我們的數(shù)組里存的是一個一個的的回調(diào)函數(shù),也就是一個一個。 今天我們來自己手寫一個符合PromiseA+規(guī)范的Promise庫。大家是不是很激動呢?? showImg(https://segmentfault.com/img/bV6t4Z?...

    joyvw 評論0 收藏0
  • 手寫一款符合Promise/A+規(guī)范Promise

    摘要:手寫一款符合規(guī)范的長篇預(yù)警有點長,可以選擇性觀看。初始狀態(tài)是,狀態(tài)可以有或者不能從轉(zhuǎn)換為或者從轉(zhuǎn)換成即只要由狀態(tài)轉(zhuǎn)換為其他狀態(tài)后,狀態(tài)就不可變更。 手寫一款符合Promise/A+規(guī)范的Promise 長篇預(yù)警!有點長,可以選擇性觀看。如果對Promise源碼不是很清楚,還是推薦從頭看,相信你認(rèn)真從頭看到尾,并且去實際操作了,肯定會有收獲的。主要是代碼部分有點多,不過好多都是重復(fù)的,不...

    rubyshen 評論0 收藏0
  • 手寫一個符合A+規(guī)范Promise

    摘要:本文同時也發(fā)布在我的博客上,歡迎之前也手寫過簡單的,這次則是為了通過官方的測試集,借鑒了一些下載量較多的,改了幾遍,終于是通過了規(guī)范的個測試用例如何測試測試庫地址在這,大家在寫完自己的后,不妨也去測試一下,檢驗自己的是否符合規(guī)范。 本文同時也發(fā)布在我的github博客上,歡迎star~ 之前也手寫過簡單的promise,這次則是為了通過官方的Promise A+測試集,借鑒了一些下載量...

    jsummer 評論0 收藏0
  • 手寫一個符合promise/A+規(guī)范promise

    摘要:使用及原理分析通過關(guān)鍵字創(chuàng)建實例接受一個參數(shù)方法返回兩個方法可用通過在方法中通過調(diào)用使成功或調(diào)用使失敗來控制狀態(tài)中可以執(zhí)行同步代碼也可以執(zhí)行異步代碼原型對象上有方法供實例調(diào)用方法接受兩個參數(shù)默認(rèn)為一個函數(shù)默認(rèn)為一個函數(shù)當(dāng)狀態(tài)為時執(zhí)行用戶傳入 promise使用及原理分析: 通過new關(guān)鍵字創(chuàng)建promise實例, 接受一個executor參數(shù), executor方法返回兩個方法 res...

    venmos 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<