一、promise解決了哪些問題?
- 異步並發 待所有異步任務結束之后再執行我們的業務邏輯。
- 回調嵌套
二、promise特點
三、簡單示例
new Promise((resolve,reject) => { // 1.狀態從pending改為fulfilled成功態,執行成功的回調 resolve('success') // 2.狀態從pending改為rejected失敗態,執行失敗的回調 // reject('fail') // throw new Error('fail') }).then((data) => { console.log('success', data); }, (err) => { console.log('fail', err); })
四、手寫實現
0.0.1版
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MyPromise { constructor(executor) { // 初始狀態為pending等待狀態 this.status = PENDING; const resolve = (value) => { // 執行resolve狀態變為成功態 this.status = FULFILLED; }; const reject = (reason) => { // 執行reject狀態變為失敗態 this.status = REJECTED; }; try { // 執行傳入的回調函數 executor(resolve, reject); } catch (e) { // 回調函數執行出錯,也會執行reject reject(e); } } }
由於promise的狀態一旦轉成了成功態或者失敗態,就不能再改變狀態了,所以我們需要加個判斷,只有當狀態為pending等待態的時候,才能將狀態改為成功或失敗,所以當狀態變為了成功,再去調用resolve的話,無法滿足判斷條件,就不會繼續執行了。
0.0.2版
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MyPromise { constructor(executor) { // 初始狀態為pending等待狀態 this.status = PENDING; const resolve = (value) => { // 執行resolve狀態變為成功態 if (this.status === PENDING) { this.status = FULFILLED; } }; const reject = (reason) => { // 執行reject狀態變為失敗態 if (this.status === PENDING) { this.status = REJECTED; } }; try { // 執行傳入的回調函數 executor(resolve, reject); } catch (e) { // 回調函數執行出錯,也會執行reject reject(e); } } }
這樣調用的話,狀態改變之后就不會再改了
let p = new MyPromise((resolve,reject) => { resolve('success') reject('success') }) console.log('p: ', p);
接下來我們實現一下promise實例的then方法,then方法的特點在上面有提到哦
0.0.3版
class MyPromise { constructor(executor) { // 賦值到this上是為了方便在then方法中調用 // 初始狀態為pending等待狀態 this.status = PENDING; // 成功回調的參數 this.value = undefined; // 失敗回調的參數 this.reason = undefined; const resolve = (value) => { // 執行resolve狀態變為成功態 保存用戶傳入的參數 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; } }; const reject = (reason) => { // 執行reject狀態變為失敗態 保存用戶傳入的參數 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; } }; try { // 執行傳入的回調函數 executor(resolve, reject); } catch (e) { // 回調函數執行出錯,也會執行reject reject(e); } } // 接收兩個函數作為參數,參數是用戶傳的,傳的第一個回調就代表成功的回調,傳的第二個回調就代表失敗的回調 then(onFulfilled, onRejected) { // 根據當前的狀態,執行對應的回調。回調的參數為用戶調用resolve或者reject傳入的數據 if (this.status === FULFILLED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } } }
調用時
let p = new MyPromise((resolve,reject) => { resolve('success') }) p.then((value) => { console.log(value);// success }, (reason) => { console.log(reason); })
但是如果是在異步代碼里面調用resolve方法,就不會執行成功回調了,例如:
let p = new MyPromise((resolve,reject) => { setTimeout(() => { resolve('success') }, 300); }) p.then((value) => { console.log(value);// success }, (reason) => { console.log(reason); })
這是因為代碼執行到settimeout,會將回調放入宏任務隊列,而不是立即執行,所以會先執行then方法,這個時候settimeout並未執行,所以resolve也未執行,此時的status = 'pending',then方法的兩個回調都不會執行。
因此,我們想要執行異步代碼里的resolve或者reject函數的話,需要在執行then方法的時候,先把成功回調或失敗回調先存起來,直到異步代碼執行到resolve,再依次執行回調。實現方式就是采用發布訂閱模式,借助兩個數組,一個是成功回調的數組,一個是失敗回調的數組,在調用then時,若status = pending,就把回調存入對應的數組,之后在resolve(reject)函數中依次調用成功(失敗)回調的數組的回調。
0.0.4版
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MyPromise { constructor(executor) { // 初始狀態為pending等待狀態 this.status = PENDING; // 成功回調的參數 this.value = undefined; // 失敗回調的參數 this.reason = undefined; this.onFulFilledCallbacks = []; // 存放成功的回調 this.onRejectedCallbacks = []; // 存放失敗的回調 const resolve = (value) => { // 執行resolve狀態變為成功態 保存用戶傳入的參數 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulFilledCallbacks.forEach((fn) => fn()); // 依次執行成功回調隊列的回調 } }; const reject = (reason) => { // 執行reject狀態變為失敗態 保存用戶傳入的參數 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach((fn) => fn()); // 依次執行失敗回調隊列的回調 } }; try { // 執行傳入的回調函數 executor(resolve, reject); } catch (e) { // 回調函數執行出錯,也會執行reject reject(e); } } // 接收兩個函數作為參數,參數是用戶傳的,傳的第一個回調就代表成功的回調,傳的第二個回調就代表失敗的回調 then(onFulfilled, onRejected) { // 根據當前的狀態,執行對應的回調。回調的參數為用戶調用resolve或者reject傳入的數據 if (this.status === FULFILLED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } // 狀態為pending時 將用戶傳的回調存放到各自的隊列中(若用戶沒有調用resolve或reject,則不會執行隊列中的回調) if (this.status === PENDING) { this.onFulFilledCallbacks.push(() => { onFulfilled(this.value); }); this.onRejectedCallbacks.push(() => { onRejected(this.reason); }); } } }
調用時
let p = new MyPromise((resolve,reject) => { setTimeout(() => { resolve('success') }, 300); }) p.then((value) => { console.log(value, 1);// success 1 }, (reason) => { console.log(reason); }) p.then((value) => { console.log(value, 2);// success 2 }, (reason) => { console.log(reason); })
接下來我們來分析then鏈式調用的規則
(1)如果then方法中成功回調或失敗回調返回的是一個非promise值,則將這個值傳遞給外層下一次then的成功回調參數
(2)如果then方法中成功回調或失敗回調的執行報錯了,則將錯誤信息傳遞給外層下一次then的失敗回調參數
let pp = new Promise((resolve,reject) => { resolve(1) }).then((data) => { console.log('第一次 success', data);//第一次 success 1 return 100; // throw new Error('error') }, (err) => { console.log('第一次 fail', err); }).then((data) => { console.log('第二次 success', data);//第二次 success 100 }, (err) => { console.log('第二次 fail', err); })
(3)如果then方法中成功回調返回的是一個promise值,
- 若在返回的這個promise內部調用了resolve函數,則將傳入resolve的參數 傳遞給外層下一次then的成功回調參數;
- 若在返回的這個promise內部調用了reject函數,則將傳入reject的參數,傳遞給外層下一次then的失敗回調參數;
let pp2 = new Promise((resolve,reject) => { resolve(1) }).then((data) => { console.log('第一次 success', data);//第一次 success 1 return new Promise((resolve, reject) => { resolve(100); // reject(200) }); }, (err) => { console.log('第一次 fail', err); }).then((data) => { console.log('第二次 success', data);//第二次 success 100 }, (err) => { console.log('第二次 fail', err); })
(4)catch方法相當於then(null, err => {})
let pp2 = new Promise((resolve,reject) => { resolve(1) }).then((data) => { console.log('第一次 success', data);//第一次 success 1 return new Promise((resolve, reject) => { // resolve(100); reject(200); }); }, (err) => { console.log('第一次 fail', err); }).catch((data) => { console.log('第二次 fail', data);//第二次 fail 200 });
then的鏈式調用是如何實現的?
每次調用then,返回一個新的promise實例,這個實例上肯定也有then方法,就可以一直.then下去
0.0.5版
class MyPromise { constructor(executor) { // 省略了跟上一版一樣的內容 } then(onFulfilled, onRejected) { // 調用then的時候 會創建一個新的promise實例並返回 let promise2 = new MyPromise((resolve, reject) => { // 這里面的resolve和reject是promise2的 當在promise2里面調用resolve,就會執行promise2.then里面的成功回調 if (this.status === FULFILLED) { // 需要拿到成功回調的返回值,傳遞給下一個then let x = onFulfilled(this.value); resolve(x); } if (this.status === REJECTED) { let x = onRejected(this.reason); resolve(x); } if (this.status === PENDING) { this.onFulFilledCallbacks.push(() => { let x = onFulfilled(this.value); resolve(x); }); this.onRejectedCallbacks.push(() => { let x = onRejected(this.reason); resolve(x); }); } }); return promise2; } }
這里涉及到MyPromise函數的遞歸執行,在調用函數時進行拆分,就好分析了
let pp = new Promise((resolve,reject) => { // resolve(1) reject(1) }) // 調用pp的then會返回promise2 let promise2 = pp.then(data => { // 執行pp的成功回調 返回普通值x 會觸發promise2的成功回調 相當於在這個promise2的內部調用resolve(x) // return 100; }, err => { // 執行pp的失敗回調 也會觸發promise2的成功回調 return 100; }) promise2.then((data) => { console.log(data);// 100 }, (err) => { console.log(err); })
上個版本只是實現了規則(1),處理的是成功或失敗回調返回非promise值的情況,下面處理一下成功或失敗的回調在執行時發生報錯的情況,只需要加上try catch即可
0.0.6版
class MyPromise { constructor(executor) { // 此處省略 } then(onFulfilled, onRejected) { // 調用then的時候 會創建一個新的promise實例並返回 let promise2 = new MyPromise((resolve, reject) => { // 這里面的resolve和reject是promise2的 當在promise2里面調用resolve,就會執行promise2.then里面的成功回調 if (this.status === FULFILLED) { // 需要拿到成功回調的返回值,傳遞給下一個then try { let x = onFulfilled(this.value); resolve(x); } catch (e) { reject(e); } } if (this.status === REJECTED) { try { let x = onRejected(this.reason); resolve(x); } catch (e) { reject(e); } } if (this.status === PENDING) { this.onFulFilledCallbacks.push(() => { try { let x = onFulfilled(this.value); resolve(x); } catch (e) { reject(e); } }); this.onRejectedCallbacks.push(() => { try { let x = onRejected(this.reason); resolve(x); } catch (e) { reject(e); } }); } }); return promise2; } }
接下來處理規則(3),也就是then方法中返回一個promise值的情況,需要給返回值注冊成功和失敗的回調,成功回調中執行promise2的resolve,失敗回調中執行promise2的reject
規則(4)實際就是調用了then方法
0.0.7版(最終版)
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function resolvePromise(x, promise2, resolve, reject) { // then中成功或失敗回調的返回值 不能和調用then方法返回的promise值相等 if (x === promise2) { return reject(new TypeError('出錯')); } if (x instanceof MyPromise) { try { let then = x.then; then.call( x, (y) => { // y代表返回的promise值內部調用resolve時傳的參數 傳給promise2的resolve方法並執行 resolve(y); }, (r) => { // r代表返回的promise值內部調用reject時傳的參數 傳給promise2的reject方法並執行 reject(r); }, ); } catch (e) { reject(e); } } else { // 非promise值 resolve(x); } } class MyPromise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onFulFilledCallbacks = []; // 存放成功的回調 this.onRejectedCallbacks = []; // 存放失敗的回調 const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulFilledCallbacks.forEach((fn) => fn()); // 依次執行成功回調隊列的回調 } }; const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach((fn) => fn()); // 依次執行失敗回調隊列的回調 } }; try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { let promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 使用settimeout是為了異步執行處理返回值的代碼 否則直接使用promise2會報錯 setTimeout(() => { // 需要拿到成功回調的返回值,傳遞給下一個then try { let x = onFulfilled(this.value); // 統一處理返回值 resolvePromise(x, promise2, resolve, reject); } catch (e) { console.log(e); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onFulFilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; }
}
調用時
let pp = new MyPromise((resolve,reject) => { resolve(1) }) // 調用pp的then會返回promise2 let promise2 = pp.then(data => { // 執行pp的成功回調 返回promise值x 會給x注冊成功和失敗回調, // 成功回調中執行promise2的成功回調,失敗回調中執行promise2的失敗回調 return new MyPromise((res, rej) => { res(3000) }) }) // 若執行promise類型的返回值的resolve,就會執行第一個回調 promise2.then((data) => { console.log(data);// 3000 }, (err) => { console.log(err); })
好啦!終於整理完成了!看完之后是不是發現promise其實也沒那么難了!