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狀態,每當調用道resolvereject方法時,就會改變其值,在後面的then方法中會用到
  • valueresolve回調成功後,調用resolve方法裡面的參數值
  • reasonreject回調成功後,調用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);
}
}

  • 為什麼要在一開始判斷promise2x?:首先在Promise/A+中寫了需要判斷這兩者如果相等,需要拋出異常,我就來解釋一下為什麼,如果這兩者相等,我們可以看下下面的例子,第一次p2p1.then出來的結果是個Promise對象,這個Promise對象在被創建的時候調用了resolvePromise(promise2,x,resolve,reject)函數,又因為x等於其本身,是個Promise,就需要then方法遞歸它,直到他不是Promise對象,但是x(p2)的結果還在等待,他卻想執行自己的then方法,就會導致等待。
  • 為什麼要遞歸去調用resolvePromise函數?:相信細心的人已經發現了,我這裡使用了遞歸調用法,首先這是Promise/A+中要求的,其次是業務場景的需求,當我們碰到那種Promiseresolve裏的Promiseresolve裏又包了一個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

推薦閱讀:

相關文章