如何從零開始利用js手寫一個(gè)Promise庫(kù)詳解
前言
ECMAScript 是 JavaScript 語(yǔ)言的國(guó)際標(biāo)準(zhǔn),JavaScript 是 ECMAScript 的實(shí)現(xiàn)。ES6 的目標(biāo),是使得 JavaScript 語(yǔ)言可以用來(lái)編寫大型的復(fù)雜的應(yīng)用程序,成為企業(yè)級(jí)開發(fā)語(yǔ)言。
概念
ES6 原生提供了 Promise 對(duì)象。
所謂 Promise,就是一個(gè)對(duì)象,用來(lái)傳遞異步操作的消息。它代表了某個(gè)未來(lái)才會(huì)知道結(jié)果的事件(通常是一個(gè)異步操作),并且這個(gè)事件提供統(tǒng)一的 API,可供進(jìn)一步處理。
三道思考題
剛開始寫前端的時(shí)候,處理異步請(qǐng)求經(jīng)常用callback,簡(jiǎn)單又順手。后來(lái)寫著寫著就拋棄了callback,開始用promise來(lái)處理異步問題。promise寫起來(lái)確實(shí)更加優(yōu)美,但由于缺乏對(duì)它內(nèi)部結(jié)構(gòu)的深刻認(rèn)識(shí),每次在遇到一些復(fù)雜的情況時(shí),promise用起來(lái)總是不那么得心應(yīng)手,debug也得搞半天。
所以,這篇文章我會(huì)帶大家從零開始,手寫一個(gè)基本能用的promise。跟著我寫下來(lái)以后,你會(huì)對(duì)promise是什么以及它的內(nèi)部結(jié)構(gòu)有一個(gè)清楚的認(rèn)知,未來(lái)在復(fù)雜場(chǎng)景下使用promise也能如魚得水。
而且,為了檢驗(yàn)大家是否真的完全掌握了promise,我會(huì)在文章結(jié)尾出幾道跟promise相關(guān)的練習(xí)題。說(shuō)是練習(xí)題,其實(shí)都是大家項(xiàng)目中會(huì)遇到的真實(shí)場(chǎng)景的抽象,熟練掌握可以幫助大家在前端方面更上一層樓。
提前將三道練習(xí)題給出來(lái),大家可以先不看下文的內(nèi)容,在腦海里大概構(gòu)思下你會(huì)怎么解決:
- promise array的鏈?zhǔn)秸{(diào)用?
- promise怎么做并發(fā)控制?
- promise怎么做異步緩存?
以上三道思考題其實(shí)跟你用不用promise并沒有多大關(guān)系,但是如果你不深刻理解promise想要解決這三個(gè)問題還真不是那么輕松的。
什么是Promise
回到正文,什么是Promise?說(shuō)白了,promise就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。
首先,ES6規(guī)定Promise對(duì)象是一個(gè)構(gòu)造函數(shù),用來(lái)生成Promise實(shí)例。然后,這個(gè)構(gòu)造函數(shù)接受一個(gè)函數(shù)(executor)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是resolve和reject。最后,Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)(onFulfilled和onRejected)。
具體的使用方法,用代碼表現(xiàn)是這樣:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
理解了這個(gè)后,我們就可以大膽的開始構(gòu)造我們自己的promise了,我們給它取個(gè)名字:CutePromise
實(shí)現(xiàn)一個(gè)Promise:CutePromise
我們直接用ES6的class來(lái)創(chuàng)建我們的CutePromise,對(duì)ES6語(yǔ)法還不熟悉的,可以先讀一下我的另外兩篇介紹ES6核心語(yǔ)法的文章后再回來(lái)。30分鐘掌握ES6/ES2015核心內(nèi)容(上) 、30分鐘掌握ES6/ES2015核心內(nèi)容(下)
class CutePromise {
// executor是我們實(shí)例化CutePromise時(shí)傳入的參數(shù)函數(shù),它接受兩個(gè)參數(shù),分別是resolve和reject。
// resolve和reject我們將會(huì)定義在constructor當(dāng)中,供executor在執(zhí)行的時(shí)候調(diào)用
constructor(executor) {
const resolve = () => {}
const reject = () => {}
executor(resolve, reject)
}
// 為實(shí)例提供一個(gè)then的方法,接收兩個(gè)參數(shù)函數(shù),
// 第一個(gè)參數(shù)函數(shù)必傳,它會(huì)在promise已成功(fulfilled)以后被調(diào)用
// 第二個(gè)參數(shù)非必傳,它會(huì)在promise已失敗(rejected)以后被調(diào)用
then(onFulfilled, onRejected) {}
}
創(chuàng)建了我們的CutePromise后,我們?cè)賮?lái)搞清楚一個(gè)關(guān)鍵點(diǎn):Promise 對(duì)象的狀態(tài)。
Promise 對(duì)象通過自身的狀態(tài),來(lái)控制異步操作。一個(gè)Promise 實(shí)例具有三種狀態(tài):
- 異步操作未完成(pending)
- 異步操作成功(fulfilled)
- 異步操作失敗(rejected)
上面三種狀態(tài)里面,fulfilled和rejected合在一起稱為resolved(已定型)。狀態(tài)的切換只有兩條路徑:第一種是從pending=>fulfilled,另一種是從pending=>rejected,狀態(tài)一旦切換就不能再改變。
現(xiàn)在我們來(lái)為CutePromise添加狀態(tài),大概流程就是:
首先,實(shí)例化初始過程中,我們先將狀態(tài)設(shè)為PENDING,然后當(dāng)executor執(zhí)行resolve的時(shí)候,將狀態(tài)更改為FULFILLED,當(dāng)executor執(zhí)行reject的時(shí)候?qū)顟B(tài)更改為REJECTED。同時(shí)更新實(shí)例的value。
constructor(executor) {
...
this.state = 'PENDING';
...
const resolve = (result) => {
this.state = 'FULFILLED';
this.value = result;
}
const reject = (error) => {
this.state = 'REJECTED';
this.value = error;
}
...
}
再來(lái)看下我們的then函數(shù)。then函數(shù)的兩個(gè)參數(shù),onFulfilled表示當(dāng)promise異步操作成功時(shí)調(diào)用的函數(shù),onRejected表示當(dāng)promise異步操作失敗時(shí)調(diào)用的函數(shù)。假如我們調(diào)用then的時(shí)候,promise已經(jīng)執(zhí)行完成了(當(dāng)任務(wù)是個(gè)同步任務(wù)時(shí)),我們可以直接根據(jù)實(shí)例的狀態(tài)來(lái)執(zhí)行相應(yīng)的函數(shù)。假如promise的狀態(tài)還是PENDING, 那我們就將onFulfilled和onRejected直接存儲(chǔ)到chained這個(gè)變量當(dāng)中,等promise執(zhí)行完再調(diào)用。
constructor(executor) {
...
this.state = 'PENDING';
// chained用來(lái)儲(chǔ)存promise執(zhí)行完成以后,需要被依次調(diào)用的一系列函數(shù)
this.chained = [];
const resolve = (result) => {
this.state = 'FULFILLED';
this.value = result;
// promise已經(jīng)執(zhí)行成功了,可以依次調(diào)用.then()函數(shù)里的onFulfilled函數(shù)了
for (const { onFulfilled } of this.chained) {
onFulfilled(res);
}
}
...
}
then(onFulfilled, onRejected) {
if (this.state === 'FULFILLED') {
onFulfilled(this.value);
} else if (this.state === 'REJECTED') {
onRejected(this.value);
} else {
this.$chained.push({ onFulfilled, onRejected });
}
}
這樣我們就完成了一個(gè)CutePromise的創(chuàng)建,下面是完整代碼,大家可以復(fù)制代碼到控制臺(tái)測(cè)試一下:
class CutePromise {
constructor(executor) {
if (typeof executor !== 'function') {
throw new Error('Executor must be a function');
}
this.state = 'PENDING';
this.chained = [];
const resolve = res => {
if (this.state !== 'PENDING') {
return;
}
this.state = 'FULFILLED';
this.internalValue = res;
for (const { onFulfilled } of this.chained) {
onFulfilled(res);
}
};
const reject = err => {
if (this.state !== 'PENDING') {
return;
}
this.state = 'REJECTED';
this.internalValue = err;
for (const { onRejected } of this.chained) {
onRejected(err);
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'FULFILLED') {
onFulfilled(this.internalValue);
} else if (this.$state === 'REJECTED') {
onRejected(this.internalValue);
} else {
this.chained.push({ onFulfilled, onRejected });
}
}
}
提供一下測(cè)試代碼:
let p = new CutePromise(resolve => {
setTimeout(() => resolve('Hello'), 100);
});
p.then(res => console.log(res));
p = new CutePromise((resolve, reject) => {
setTimeout(() => reject(new Error('woops')), 100);
});
p.then(() => {}, err => console.log('Async error:', err.stack));
p = new CutePromise(() => { throw new Error('woops'); });
p.then(() => {}, err => console.log('Sync error:', err.stack));
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
使用js正則表達(dá)式驗(yàn)證文件擴(kuò)展名方法實(shí)例
文件上傳時(shí),為了安全驗(yàn)證,對(duì)于手工改動(dòng)文件后綴名產(chǎn)生的偽造文件進(jìn)行判斷過濾,下面這篇文章主要給大家介紹了關(guān)于如何使用js正則表達(dá)式驗(yàn)證文件擴(kuò)展名的相關(guān)資料,需要的朋友可以參考下2022-11-11
JS面向?qū)ο缶幊袒A(chǔ)篇(一) 對(duì)象和構(gòu)造函數(shù)實(shí)例詳解
這篇文章主要介紹了JS面向?qū)ο缶幊虒?duì)象和構(gòu)造函數(shù),結(jié)合實(shí)例形式詳細(xì)分析了JS面向?qū)ο缶幊虒?duì)象和構(gòu)造函數(shù)具體概念、原理、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
javascript中的__defineGetter__和__defineSetter__介紹
這篇文章主要介紹了javascript中的__defineGetter__和__defineSetter__介紹,類似面向?qū)ο笳Z(yǔ)言中的get和set關(guān)鍵字,需要的朋友可以參考下2014-08-08
javascript 進(jìn)度條 實(shí)現(xiàn)代碼
這個(gè)例子是通過測(cè)試的。是真真正正根據(jù)記錄的條數(shù)掛鉤的。2009-07-07
2020淘寶618理想生活列車自動(dòng)領(lǐng)喵幣js腳本的代碼
這篇文章主要介紹了2020淘寶618理想生活列車自動(dòng)領(lǐng)喵幣腳本,需要先安裝 auto.js腳本,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
JavaScript模擬深藍(lán)vs卡斯帕羅夫的國(guó)際象棋對(duì)局示例
這篇文章主要介紹了JavaScript模擬深藍(lán)vs卡斯帕羅夫的國(guó)際象棋對(duì)局示例,使用javascript較為逼真的模擬出了國(guó)際象棋對(duì)弈的場(chǎng)景,需要的朋友可以參考下2015-04-04

