這是一個簡版非規範的Promise實現,其中前置知識點比較重要,理解了前置知識點的內容,也就瞭解了Promise最基本的原理。數組
這小節的核心是:把函數名當變量用。promise
如下三點理解以後,就可以實現簡單的Promise了。瀏覽器
舉個栗子bash
let myFn = function () {};
複製代碼
你能夠直接調用異步
myFn();
複製代碼
和下面的代碼效果是同樣的函數
function myFn() {
// code here...
}
myFn();
複製代碼
let fnArray = [];
let fn1 = () => { console.log(1) };
let fn2 = () => { console.log(2) };
fnArray.push(fn1);
fnArray.push(fn2);
fnArray.forEach(cb => cb());
複製代碼
這段代碼的運行結果是ui
1
2
複製代碼
function showYouFn(fn1, fn2) {
// 調用
fn1();
fn2();
}
let myFn1 = () => { console.log(3); };
let myFn2 = () => { console.log(4); };
showYouFn(myFn1, myFn2);
複製代碼
回想一下:把函數名當變量用。this
下面是一個簡陋得不像Promise的Promise實現spa
function MyPromise(fn) {
function resolve(value) {
console.log(value);
}
function reject(value) {
console.log(value);
}
fn(resolve, reject);
}
複製代碼
如今你就能夠用上自定義的Promise了prototype
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
});
複製代碼
將會在2秒後輸出
你看到我啦!
複製代碼
下面的代碼效果和上面的同樣,寫出來是爲了再次強調:函數當變量用。
let promiseFn = (resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
};
new MyPromise(promiseFn);
複製代碼
解釋一下總體代碼:
MyPromise
中的參數 fn
是須要用戶傳入的自定義函數(該函數須要接收兩個參數)。
MyPromise
中的內部還有兩個函數 resolve
和 reject
,其中 resolve
表示用戶的異步任務成功時應該調用的函數,reject
表示用戶的異步任務失敗時該調用的函數。
那用戶如何調用 resolve
和 reject
呢?
很簡單,把兩個函數看成 fn
的參數傳遞出去便可。
因此 MyPromise
內部在調用 fn
時會把 resolve
和 reject
看成參數傳遞給 fn
。
而後用戶在自定義函數內調用 resolve
或 reject
來通知 MyPromise
異步任務已經執行完了。
建議複製下面的代碼去控制檯試試
function MyPromise(fn) {
function resolve(value) {
console.log(value);
}
function reject(value) {
console.log(value);
}
fn(resolve, reject);
}
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
});
複製代碼
經過上面的代碼能夠發現一個問題,咱們不知道Promise的異步任務進行到哪一步了、是成功仍是失敗了。
因此增長三個狀態用來標識一個Promise的異步任務進行到何種程度了。
pending
、resolved
、rejected
分別表示 執行中、已完成、已失敗。
而後經過觀察用戶調用的是 resolve
仍是 reject
能夠判斷當前Promise的狀態。
那麼會有三種狀況:
resolve
或 reject
以前狀態是 pending
resolve
時,狀態將變爲 resolved
reject
時,狀態將變爲 rejected
下面進行代碼的改造,定義了三個常量表示狀態以及一個變量 state
用來存儲當前狀態。
而且當 resolve
被調用時將 state
修改成 resolved
。
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
// 初始時狀態爲執行中,即pending
that.state = PENDING;
function resolve(value) {
console.log(value);
// resolve被調用時修改狀態
that.state = RESOLVED;
}
function reject(value) {
console.log(value);
// reject被調用時修改狀態
that.state = REJECTED;
}
fn(resolve, reject);
}
複製代碼
OK,如今已經能知道Promise當前所處的狀態了,可是任務完了得拿到結果吧,MyPromise
的 resolve
被調用,那也只是MyPromise
知道任務完成了,用戶還不知道呢。
因此咱們須要回調函數告訴用戶,是的,其實就是回調函數。
這時候就輪到 then
方法出場了,用戶經過then
方法傳入回調函數, MyPromise
將在成功調用 resolve
時調用用戶傳入的回調函數。
開始改造代碼,MyPromise
內部須要變量存儲回調函數,then
方法的做用就是將用戶傳入的回調函數賦予 MyPromise
內的變量。
因此 then
方法長這樣,接收兩個參數,一個是成功時的回調函數,一個是失敗時的回調函數。
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 兩個存儲回調函數的變量
that.resolvedCallback;
that.rejectedCallback;
function resolve(value) {
console.log(value);
that.state = RESOLVED;
// 調用用戶的回調函數,並告知結果
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(value) {
console.log(value);
that.state = REJECTED;
// 調用用戶的回調函數,並告知結果
that.rejectedCallback && that.rejectedCallback(value);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallback = onFulfilled;
that.rejectedCallback = onRejected;
}
複製代碼
是的,一個簡版Promise幾乎大功告成,讓咱們再試試在瀏覽器執行以下代碼(注意我刪除了 resolve
和 reject
裏的console語句)
(function () {
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 兩個存儲回調函數的變量
that.resolvedCallback;
that.rejectedCallback;
function resolve(value) {
that.state = RESOLVED;
// 調用用戶的回調函數,並告知結果
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(value) {
that.state = REJECTED;
// 調用用戶的回調函數,並告知結果
that.rejectedCallback && that.rejectedCallback(value);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallback = onFulfilled;
that.rejectedCallback = onRejected;
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('你又看到我啦~');
}, 2000);
}).then(value => {
console.log(value);
});
})();
複製代碼
上面的代碼,用法上已經和Promise長得差很少了,可是若是咱們屢次調用 then
方法呢?
是的,只有最後一個 then
方法裏的回調函數能執行,這固然無法知足咱們的須要。
因而,將兩個回調函數改爲函數數組(請回想一下前置知識),並在狀態更改時遍歷調用回調函數。
改造後的代碼以下:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 注意這裏變成了兩個數組
that.resolvedCallbackArray = [];
that.rejectedCallbackArray = [];
function resolve(value) {
that.state = RESOLVED;
// 遍歷用戶的回調函數,告知結果
that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
}
function reject(value) {
that.state = REJECTED;
// 遍歷用戶的回調函數,告知結果
that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallbackArray.push(onFulfilled);
that.rejectedCallbackArray.push(onRejected);
// 這裏是爲了鏈式調用,即myPromise.then().then().then()...
return that;
}
複製代碼
如今咱們能夠屢次調用 then
方法了。試試在控制檯運行下面的代碼:
(function () {
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 注意這裏變成了兩個數組
that.resolvedCallbackArray = [];
that.rejectedCallbackArray = [];
function resolve(value) {
that.state = RESOLVED;
// 遍歷用戶的回調函數,告知結果
that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
}
function reject(value) {
that.state = REJECTED;
// 遍歷用戶的回調函數,告知結果
that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallbackArray.push(onFulfilled);
that.rejectedCallbackArray.push(onRejected);
return that;
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('你又看到我啦~');
}, 2000)
}).then(value => {
console.log('第一次', value);
}).then(value => {
console.log('第二次', value);
});
})();
複製代碼
上面已是簡版Promise的實現了。
可是咱們還能夠更完善一點,加強 MyPromise
的健壯性。
例如,若用戶自定義函數在執行過程當中發生了錯誤,會中斷程序的執行,因而咱們增長try...catch...
語句,並在發生錯誤時主動執行reject
函數告知用戶。
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
複製代碼
又或者,對參數進行校驗,狀態進行判斷等,以 then
爲例,若用戶傳入的參數不是函數呢? 或者Promise的狀態已經時rejected
或resolved
,此時調用then
呢?
改造 then
後代碼以下:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if(typeof onRejected !== 'function') {
onRejected = v => v;
}
if(typeof onFulfilled !== 'function') {
onFulfilled = v => { throw r };
}
const that = this;
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
複製代碼
還有其餘方法在理解了基本原理以後能夠本身根據規範改造,本文到此告一段落,感謝閱讀。