剛最先寫前端的時刻,處置懲罰異步要求經經常運用callback,簡樸又隨手。厥後寫着寫着就揚棄了callback,最先用promise來處置懲罰異步題目。promise寫起來確切越發幽美,但由於缺少對它內部結構的深入熟習,每次在碰到一些龐雜的狀況時,promise用起來老是不那末隨心所欲,debug也得搞半天。
所以,這篇文章我會帶人人從零最先,手寫一個基礎能用的promise。隨着我寫下來今後,你會對promise是什麼以及它的內部結構有一個清晰的認知,將來在龐雜場景下運用promise也能瓮中之鱉。
什麼是Promise
回到正文,什麼是Promise?說白了,promise就是一個容器,內里保存着某個將來才會完畢的事宜(通常是一個異步操縱)的效果。
起首,ES6劃定Promise對象是一個組織函數,用來天生Promise實例。然後,這個組織函數吸收一個函數(executor)作為參數,該函數的兩個參數離別是resolve和reject。末了,Promise實例天生今後,可以用then要領離別指定resolved狀況和rejected狀況的回調函數(onFulfilled和onRejected)。
詳細的運用要領,用代碼錶現是如許:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操縱勝利 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
理解了這個后,我們就可以斗膽勇敢的最先組織我們自身的promise了,我們給它取個名字:CutePromise
完成一個Promise:CutePromise
我們直接用ES6的class來建立我們的CutePromise,對ES6語法還不熟習的,可以先讀一下我的別的兩篇引見ES6中心語法的文章后再回來。30分鐘掌握ES6/ES2015中心內容(上)、30分鐘掌握ES6/ES2015中心內容(下)
class CutePromise {
// executor是我們實例化CutePromise時傳入的參數函數,它吸收兩個參數,離別是resolve和reject。
// resolve和reject我們將會定義在constructor當中,供executor在實行的時刻挪用
constructor(executor) {
const resolve = () => {}
const reject = () => {}
executor(resolve, reject)
}
// 為實例供應一個then的要領,吸收兩個參數函數,
// 第一個參數函數必傳,它會在promise已勝利(fulfilled)今後被挪用
// 第二個參數非必傳,它會在promise已失利(rejected)今後被挪用
then(onFulfilled, onRejected) {}
}
建立了我們的CutePromise后,我們再來搞清晰一個癥結點:Promise 對象的狀況。
Promise 對象經由過程自身的狀況,來掌握異步操縱。一個Promise 實例具有三種狀況:
- 異步操縱未完成(pending)
- 異步操縱勝利(fulfilled)
- 異步操縱失利(rejected)
上面三種狀況內里,fulfilled和rejected合在一起稱為resolved(已定型)。狀況的切換隻要兩條途徑:第一種是從pending=>fulfilled,另一種是從pending=>rejected,狀況一旦切換就不能再轉變。
如今我們來為CutePromise增加狀況,也許流程就是:
起首,實例化初始過程當中,我們先將狀況設為PENDING
,然後當executor實行resolve的時刻,將狀況更改成FULFILLED
,當executor實行reject的時刻將狀況更改成REJECTED
。同時更新實例的value。
constructor(executor) {
...
this.state = 'PENDING';
...
const resolve = (result) => {
this.state = 'FULFILLED';
this.value = result;
}
const reject = (error) => {
this.state = 'REJECTED';
this.value = error;
}
...
}
再來看下我們的then函數。then函數的兩個參數,onFulfilled示意當promise異步操縱勝利時挪用的函數,onRejected示意當promise異步操縱失利時挪用的函數。假如我們挪用then的時刻,promise已實行完成了(當使命是個同步使命時),我們可以直接依據實例的狀況來實行響應的函數。假如promise的狀況照樣PENDING, 那我們就將onFulfilled和onRejected直接存儲到chained這個變量當中,等promise實行完再挪用。
constructor(executor) {
...
this.state = 'PENDING';
// chained用來貯存promise實行完成今後,須要被順次挪用的一系列函數
this.chained = [];
const resolve = (result) => {
this.state = 'FULFILLED';
this.value = result;
// promise已實行勝利了,可以順次挪用.then()函數里的onFulfilled函數了
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 });
}
}
如許我們就完成了一個CutePromise的建立,下面是完全代碼,人人可以複製代碼到掌握台測試一下:
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 });
}
}
}
供應一下測試代碼:
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));
完成鏈式挪用
完成鏈式挪用實在很簡樸,只須要在我們定義的then()要領里返回一個新的CutePromise即可。
then(onFulfilled, onRejected) {
return new CutePromise((resolve, reject) => {
const _onFulfilled = res => {
try {
//注重這裏resolve有可能要處置懲罰的是一個promise
resolve(onFulfilled(res));
} catch (err) {
reject(err);
}
};
const _onRejected = err => {
try {
reject(onRejected(err));
} catch (_err) {
reject(_err);
}
};
if (this.state === 'FULFILLED') {
_onFulfilled(this.internalValue);
} else if (this.state === 'REJECTED') {
_onRejected(this.internalValue);
} else {
this.chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected });
}
});
}
不過,我們還須要處理一個題目:假如then函數的第一個參數onfulfilled()自身返回的也是一個promise怎麼辦?比方下面這類運用體式格局,實際上是最實在項目場景中最罕見:
p = new CutePromise(resolve => {
setTimeout(() => resolve('World'), 100);
});
p.
then(res => new CutePromise(resolve => resolve(`Hello, ${res}`))).
then(res => console.log(res));
所以我們須要讓我們的resolve要領可以處置懲罰promise:
const resolve = res => {
if (this.state !== 'PENDING') {
return;
}
// 假如說res這個對象有then的要領,我們就以為res是一個promise
if (res != null && typeof res.then === 'function') {
return res.then(resolve, reject);
}
...
}
三道思考題
- promise array的鏈式挪用?
- promise怎麼做併發掌握?
- promise怎麼做異步緩存?
以上三道思考題實在跟你用不用promise並沒有多大關聯,然則假如你不深入理解promise想要處理這三個題目還真不是那末輕鬆的。