實現一個Promise
1、constructor
首先我們都知道Promise
有三個狀態,為了方便我們把它定義成常量
const PENDING = pending;
const FULFILLED = fulfilled;
const REJECTED = rejected;
接下來我們來定義一個類
class MyPromise {
constructor(executor) {
//控制狀態,使用了一次之後,接下來的都不被使用
this.state = PENDING;
this.value = null;
this.reason = null;
// 定義resolve函數
const resolve = value => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
}
}
// 定義reject函數
const reject = value => {
if (this.state === PENDING) {
this.reason = value;
this.state = REJECTED;
}
}
// executor方法可能會拋出異常,需要捕獲
try {
// 將resolve和reject函數給使用者
executor(resolve, reject);
} catch (error) {
// 如果在函數中拋出異常則將它注入reject中
reject(error);
}
}
}
到這基本比較好理解我簡單說明一下
executor
:這是實例Promise
對象時在構造器中傳入的參數,一般是一個function(resolve,reject){}
state:``Promise
的狀態,一開始是默認的pendding
狀態,每當調用道resolve
和reject
方法時,就會改變其值,在後面的then
方法中會用到value
:resolv
e回調成功後,調用resolve
方法裡面的參數值reason
:reject
回調成功後,調用reject
方法裡面的參數值resolve
:聲明resolve
方法在構造器內,通過傳入的executor
方法傳入其中,用以給使用者回調reject
:聲明reject
方法在構造器內,通過傳入的executor
方法傳入其中,用以給使用者回調
2、then
then
就是將Promise
中的resolve
或者reject
的結果拿到,那麼我們就能知道這裡的then方法需要兩個參數,成功回調和失敗回調,上代碼!
then(onFulfilled, onRejected) {
if (this.state === FULFILLED) {
onFulfilled(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
}
我們來簡單的運行一下測試代碼
const mp = new MyPromise((resolve, reject)=> {
resolve(******* i love you *******);
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log(****** 你不愛我了*******, err)
})
// 11111 ******* i love you *******
這樣看著好像沒有問題,那麼我們來試試非同步函數呢?
const mp = new MyPromise((resolve, reject)=> {
setTimeout(()=> {
resolve(******* i love you *******);
}, 0)
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log(****** 你不愛我了*******, err)
})
我們會發現什麼也沒有列印,哪裡出問題了呢?原來是由於非同步的原因,當我們執行到then
的時候this. state
的值還沒發生改變,所以then
裡面的判斷就失效了。那麼我們該怎麼解決呢?
這就要說回經典得callback
了。來上源碼
// 存放成功回調的函數
this.onFulfilledCallbacks = [];
// 存放失敗回調的函數
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
this.onFulfilledCallbacks.map(fn => fn());
}
}
const reject = value => {
if (this.state === PENDING) {
this.value = value;
this.reason = REJECTED;
this.onRejectedCallbacks.map(fn => fn());
}
}
在then
裡面添加
then(onFulfilled, onRejected) {
// ...
if(this.state === PENDING) {
this.onFulfilledCallbacks.push(()=> {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(()=> {
onRejected(this.value);
})
}
}
好了,到這非同步的問題解決了,我們再來執行一下剛才的測試代碼。結果就出來了。到這我們還缺什麼呢?
- 鏈式調用
- 當我們不傳參數時應當什麼運行
這二個的思路也都很簡單,鏈式調用也就是說我們再返回一個promise
的實例就好了。而不傳參則就是默認值的問題了。下面來看源碼
then(onFulfilled, onRejected) {
let self = this;
let promise2 = null;
//解決onFulfilled,onRejected沒有傳值的問題
onFulfilled = typeof onFulfilled === function ? onFulfilled : y => y
//因為錯誤的值要讓後面訪問到,所以這裡也要跑出個錯誤,不然會在之後then的resolve中捕獲
onRejected = typeof onRejected === function ? onRejected : err => {
throw err;
}
promise2 = new MyPromise((resolve, reject) => {
if (self.state === PENDING) {
console.log(then PENDING)
self.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
console.log(333333, x, typeof x);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
}
if (self.state === FULFILLED) {
console.log(then FULFILLED)
setTimeout(() => {
try {
let x = onFulfilled(self.value);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
}
if (self.state === REJECTED) {
console.log(then REJECTED)
setTimeout(() => {
try {
let x = onRejected(self.reason);
self.resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
})
}
});
return promise2;
}
為什麼外面要包一層setTimeout
?:因為Promise
本身是一個非同步方法,屬於微任務一列,必須得在執行棧執行完了在去取他的值,所以所有的返回值都得包一層非同步setTimeout
。
resolvePromise
是什麼?:這其實是官方Promise/A+的需求。因為你的then
可以返回任何職,當然包括Promise
對象,而如果是Promise
對象,我們就需要將他拆解,直到它不是一個Promise
對象,取其中的值。
3、resolvePromise
我們直接看代碼
resolvePromise(promise2, x, resolve, reject) {
let self = this;
let called = false; // called 防止多次調用
//因為promise2是上一個promise.then後的返回結果,所以如果相同,會導致下面的.then會是同一個promise2,一直都是,沒有盡頭
//相當於promise.then之後return了自己,因為then會等待return後的promise,導致自己等待自己,一直處於等待
if (promise2 === x) {
return reject(new TypeError(循環引用));
}
//如果x不是null,是對象或者方法
if (x !== null && (Object.prototype.toString.call(x) === [object Object] || Object.prototype.toString.call(x) === [object Function])) {
// x是對象或者函數
try {
let then = x.then;
if (typeof then === function) {
then.call(x, (y) => {
// 別人的Promise的then方法可能設置了getter等,使用called防止多次調用then方法
if (called) return;
called = true;
// 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值為基本類型或者非thenable
self.resolvePromise(promise2, y, resolve, reject);
}, (reason) => {
if (called) return;
called = true;
reject(reason);
});
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (reason) {
if (called) return;
called = true;
reject(reason);
}
} else {
// x是普通值,直接resolve
resolve(x);
}
}
- 為什麼要在一開始判斷
promise2
和x
?:首先在Promise/A+中寫了需要判斷這兩者如果相等,需要拋出異常,我就來解釋一下為什麼,如果這兩者相等,我們可以看下下面的例子,第一次p2
是p1.then
出來的結果是個Promise
對象,這個Promise
對象在被創建的時候調用了resolvePromise(promise2,x,resolve,reject)
函數,又因為x
等於其本身,是個Promise
,就需要then
方法遞歸它,直到他不是Promise
對象,但是x(p2)
的結果還在等待,他卻想執行自己的then
方法,就會導致等待。 - 為什麼要遞歸去調用
resolvePromise
函數?:相信細心的人已經發現了,我這裡使用了遞歸調用法,首先這是Promise/A+中要求的,其次是業務場景的需求,當我們碰到那種Promise
的resolve
裏的Promise
的resolve
裏又包了一個Promise
的話,就需要遞歸取值,直到x
不是Promise
對象。
4、catch
//catch方法
catch(onRejected){
return this.then(null,onRejected)
}
5、finally
finally
方法用於指定不管 Promise
對象最後狀態如何,都會執行的操作。該方法是 ES2018
引入標準的。
finally(fn) {
return this.then(value => {
fn();
return value;
}, reason => {
fn();
throw reason;
});
};
6、resolve/reject
大家一定都看到過Promise.resolve()
、Promise.reject()
這兩種用法,它們的作用其實就是返回一個Promise對象,我們來實現一下。
static resolve(val) {
return new MyPromise((resolve, reject) => {
resolve(val)
})
}
//reject方法
static reject(val) {
return new MyPromise((resolve, reject) => {
reject(val)
})
}
7、all
all
方法可以說是Promise
中很常用的方法了,它的作用就是將一個數組的Promise
對象放在其中,當全部resolve
的時候就會執行then
方法,當有一個reject
的時候就會執行catch
,並且他們的結果也是按著數組中的順序來排放的,那麼我們來實現一下。
static all(promiseArr) {
return new MyPromise((resolve, reject) => {
let result = [];
promiseArr.forEach((promise, index) => {
promise.then((value) => {
result[index] = value;
if (result.length === promiseArr.length) {
resolve(result);
}
}, reject);
});
});
}
8、race
race方
法雖然不常用,但是在Promise
方法中也是一個能用得上的方法,它的作用是將一個Promise
數組放入race
中,哪個先執行完,race
就直接執行完,並從then
中取值。我們來實現一下吧。
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
promiseArr.forEach(promise => {
promise.then((value) => {
resolve(value);
}, reject);
});
});
}
9、deferred
static deferred() {
let dfd = {};
dfd.promies = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.rfeject = reject;
});
return dfd;
};
什麼作用呢?看下面代碼你就知道了
let fs = require(fs)
let MyPromise = require(./MyPromise)
//Promise上的語法糖,為了防止嵌套,方便調用
//壞處 錯誤處理不方便
function read(){
let defer = MyPromise.defer()
fs.readFile(./1.txt,utf8,(err,data)=>{
if(err)defer.reject(err)
defer.resolve(data)
})
return defer.Promise
}
10、測試
const mp1 = MyPromise.resolve(1);
const mp2 = MyPromise.resolve(2);
const mp3 = MyPromise.resolve(3);
const mp4 = MyPromise.reject(4);
MyPromise.all([mp1, mp2, mp3]).then(x => {
console.log(x);
}, (err) => {
console.log(err1, err);
})
MyPromise.race([mp1, mp4, mp2, mp3]).then(x => {
console.log(x);
}, (err) => {
console.log(err2, err);
})
var mp = new MyPromise((resolve, reject) => {
console.log(11111);
setTimeout(() => {
resolve(22222);
console.log(3333);
}, 1000);
});
mp.then(x => {
console.log(x);
}, (err) => {
console.log(err2, err);
})
//11111
//[ 1, 2, 3 ]
//1
//3333
//22222
推薦閱讀: