JavaScript Promise原理與實現(xiàn)刨析
1 Promise核心邏輯實現(xiàn)
Promise對象是一個原生的javascript對象,是一種異步編程的解決方案,它通過一個回調,可以避免更多的回調,接下來說明其原理及實現(xiàn)。
下面一段代碼是Promise的基本使用:
new Promise((resolve, reject) => { resolve("成功"); reject("失敗"); }) Promise.then(value => { }, reason => { })
從上面的代碼中,我們可以分析出一些關鍵點:
- Promise創(chuàng)建時需要使用
new
關鍵字,那么我們可以知道Promise就是一個類; - 在執(zhí)行這個類的時候,需要傳遞一個執(zhí)行器進去,這個執(zhí)行器會立即執(zhí)行;
- 在執(zhí)行器中有兩個參數(shù),
resolve
和reject
,它們都是函數(shù),用來改變Promise中的狀態(tài); - Promise中有三種狀態(tài),分別是:成功
fulfilled
、失敗rejected
和等待pending
,狀態(tài)只能從pending
—>fulfilled
,或者pending
—>rejected
,狀態(tài)一旦確定就不可以更改; resolve
和reject
函數(shù)是用來更改狀態(tài)的,其中,resolve
將狀態(tài)更改為fulfilled
,reject
將狀態(tài)更改為rejected
;then
方法接收兩個函數(shù)作為參數(shù),它需要判斷狀態(tài),如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù),并且then
方法是被定義在原型對象中的,第一個回調函數(shù)的參數(shù)為成功之后的值,第二個回調函數(shù)的參數(shù)為失敗之后的原因;
接下來我們根據(jù)上面分析出的內容,一步一步實現(xiàn)我們自己的Promise。
首先創(chuàng)建一個類,為constructor
構造函數(shù)傳入一個執(zhí)行器,因為執(zhí)行器需要立即執(zhí)行,因此在構造函數(shù)中調用該執(zhí)行器。
class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(); // 執(zhí)行器會立即執(zhí)行 } }
在執(zhí)行器中有兩個參數(shù)resolve
和reject
,它們都是函數(shù),因此在類中創(chuàng)建兩個箭頭函數(shù)resolve
和reject
,在執(zhí)行器executor
中使用this
來調用它們。
為什么使用箭頭函數(shù):
注意,我們在Promise中調用resolve
和reject
是直接調用的,如果將它們寫成普通函數(shù),那么會將this
指向window
或者undefined
,如果我們寫成箭頭函數(shù),那么它們的this
就會指向類的實例對象。
class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } resolve = () => { } reject = () => { } }
resolve
和reject
這兩個函數(shù)是用來改變狀態(tài)的,因此我們將狀態(tài)定義在類的外面,因為它們會被頻繁使用到。在類中我們默認定義狀態(tài)status
是等待pending
,當調用resolve
時,狀態(tài)改為成功,當調用reject
時,狀態(tài)改為失敗。并且狀態(tài)一旦確定不可更改,因此我們要在兩個函數(shù)中判斷當前狀態(tài)是否為等待,不是則返回。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 resolve = () => { if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 } reject = () => { if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 } }
Promise中的then
方法有兩個參數(shù),當狀態(tài)成功調用第一個,狀態(tài)失敗調用第二個,因此內部需要使用if
來判斷狀態(tài)。調用成功或者失敗函數(shù)時,需要為其傳入?yún)?shù),那么我們知道成功的值是由resolve
傳遞來的,失敗的原因是由reject
傳遞來的,因此我們在Promise中聲明兩個屬性存放兩個值。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 } then(successCallback, failCallback) { // then方法有兩個參數(shù) if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } } }
到這里我們就實現(xiàn)了一個最簡單的Promise了。
2 加入異步邏輯
上面我們實現(xiàn)的Promise,實際上并沒有考慮異步情況,比如說下面的代碼中,2秒后調用成功的回調,如果這時調用then
方法,那么當前的狀態(tài)是等待pending
,但是我們并沒有判斷狀態(tài)是pending
時的情況。
new Promise((resolve, reject) => { setTimeout(() => { resolve("成功"); }, 2000); }) Promise.then(value => { }, reason => { })
因此在then
方法中,我們應該判斷當狀態(tài)是等待的情況。當狀態(tài)是等待時,我們沒有辦法調用成功或者失敗的回調,這時我們需要將成功回調和失敗回調儲存起來。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = undefined; // 成功的回調函數(shù) failCallback = undefined; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 } then(successCallback, failCallback) { // then方法有兩個參數(shù) if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback = successCallback; this.failCallback = failCallback; } } }
將成功回調和失敗回調存儲起來之后,我們則要在resolve
和reject
方法中判斷是否存在成功或者失敗的回調,如果存在,則將其調用。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = undefined; // 成功的回調函數(shù) failCallback = undefined; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 this.successCallback && this.successCallback(this.value); // 如果成功回調存在,則調用 } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 this.failCallback && this.failCallback(this.reason); // 如果失敗回調存在,則調用 } then(successCallback, failCallback) { // then方法有兩個參數(shù) if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback = successCallback; this.failCallback = failCallback; } } }
這是我們就處理了異步的情況了。
3 then方法添加多次調用邏輯
Promise的then
方法可以調用多次,我們接著處理這部分。
let promise = new Promise((resolve, reject) => { }) promise.then(value => { }) promise.then(value => { }) promise.then(value => { })
如果多次調用了then
方法,就需要考慮兩種情況:同步情況和異步情況。如果是同步情況,那么直接就可以調用回調函數(shù),我們已經(jīng)不需要多做處理了,如果是異步情況,那么我們需要將每一個回調函數(shù)儲存起來。
我們之前在then
方法中判斷等待的時候,也將成功和失敗的回調存儲起來,但是每次只能存儲一個,因此我們需要將存儲的容器設為數(shù)組,通過數(shù)組的push
方法將回調函數(shù)存儲起來。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = []; // 使用數(shù)組存儲成功的回調函數(shù) failCallback = []; // 使用數(shù)組存儲失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 this.successCallback && this.successCallback(this.value); // 如果成功回調存在,則調用 } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 this.failCallback && this.failCallback(this.reason); // 如果失敗回調存在,則調用 } then(successCallback, failCallback) { // then方法有兩個參數(shù) if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } } }
更改成數(shù)組之后,那么我們原來在resolve
和reject
函數(shù)中調用成功或者失敗的回調函數(shù)就不可以使用了,而是在其中循環(huán)調用數(shù)組中的回調函數(shù)。
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = []; // 成功的回調函數(shù) failCallback = []; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 while (this.successCallback.length) { // 循環(huán)執(zhí)行數(shù)組中的回調函數(shù) this.successCallback.shift()(this.value); // 調用回調函數(shù) } } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 while (this.failCallback.length) { // 循環(huán)執(zhí)行 this.failCallback.shift()(this.value); // 調用失敗回調函數(shù) } } then(successCallback, failCallback) { // then方法有兩個參數(shù) if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } } }
4 鏈式調用then方法
Promise的then
方法可以鏈式調用,下一個then
方法中成功回調函數(shù)的參數(shù)是上一個then
方法中的回調函數(shù)的返回值,也就是說,在下面代碼中,value
的值為1。
let promise = new Promise((resolve, reject) => { }); promise.then(() => { return 1 }) .then(value => { }) .then(() => { })
我們首先來實現(xiàn)then
方法的鏈式調用。then
方法是Promise中的方法,如果要實現(xiàn)鏈式調用,那么每個then
方法都應該返回一個Promise對象,這樣才可以調用。那么我們應該在then
方法中創(chuàng)建一個Promise對象,最后返回這個對象就可以。除此之外,我們還需要將then
方法中原來的代碼傳入到新創(chuàng)建對象的執(zhí)行器中,保證調用方法后就立即執(zhí)行。
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise(() => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) successCallback(this.value); } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) failCallback(this.reason); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
那么鏈式調用的問題就解決了,我們還需要將本次回調函數(shù)的返回值傳到下一個then
的成功回調函數(shù)中。因此,我們需要獲取到成功和失敗回調函數(shù)的返回值,并將其通過新promise對象的resolve
方法傳遞過去。
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) let result = successCallback(this.value); resolve(result); // 將返回值傳遞到下一個回調函數(shù) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) let result = failCallback(this.reason); resolve(result); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
如果上一次回調函數(shù)的返回值是一個Promise對象,我們就需要查看Promise對象的狀態(tài),如果狀態(tài)成功,則調用resolve
將狀態(tài)傳遞給下一個Promise對象,如果狀態(tài)失敗,則調用reject
將失敗信息傳遞。我們需要寫一個方法resolvePromise
,用來判斷這些邏輯。
function resolvePromise(result, resolve, reject) { // 通過判斷result是不是MyPromise的實例對象來判斷是不是Promise對象 if (result instanceof MyPromise) { // 是Promise對象 // 調用then方法查看Promise對象的狀態(tài) // 如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù) result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接將普通值傳遞 } }
那么在then
方法中調用resolvePromise
函數(shù):
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) let result = successCallback(this.value); resolvePromise(result, resolve, reject); // 調用方法 } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) let result = failCallback(this.reason); resolvePromise(result, resolve, reject); } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
在上面我們知道then
方法中可以返回Promise對象,那么如果返回的Promise對象還是then
方法中接收到的Promise對象,則會形成循環(huán)調用,這時應該報出錯誤。我們在then
方法中拿到了返回值result
,因此只需要判斷它是不是和promise
相同即可。我們將這個邏輯也寫在resolvePromise
函數(shù)中。
function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,報錯 reject(new TypeError("promise對象循環(huán)了")); return; // 阻止代碼向下執(zhí)行 } // 通過判斷result是不是MyPromise的實例對象來判斷是不是Promise對象 if (result instanceof MyPromise) { // 是Promise對象 // 調用then方法查看Promise對象的狀態(tài) // 如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù) result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接將普通值傳遞 } }
then
方法中也調用上面的函數(shù)。但是實際上我們在then
方法中是獲取不到promise
的,因為promise
在實例化之后才可以獲取到,這時我們可以將狀態(tài)成功時的代碼變成異步代碼,讓同步代碼先執(zhí)行完成,執(zhí)行完成之后再執(zhí)行異步代碼。我們可以使用setTimeout
定時器來使其變成異步代碼。
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
全部代碼如下:
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 try { executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } catch (e) { this.reject(e); // 將錯誤原因傳遞給失敗回調函數(shù) } } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = []; // 成功的回調函數(shù) failCallback = []; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 // this.successCallback && this.successCallback(this.value); // 如果成功回調存在,則調用 while (this.successCallback.length) { // 循環(huán)執(zhí)行數(shù)組中的回調函數(shù) this.successCallback.shift()(this.value); // 調用回調函數(shù) } } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 // this.failCallback && this.failCallback(this.reason); // 如果失敗回調存在,則調用 while (this.failCallback.length) { // 循環(huán)執(zhí)行 this.failCallback.shift()(this.value); // 調用失敗回調函數(shù) } } then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,報錯 reject(new TypeError("promise對象循環(huán)了")); return; // 阻止代碼向下執(zhí)行 } // 通過判斷result是不是MyPromise的實例對象來判斷是不是Promise對象 if (result instanceof MyPromise) { // 是Promise對象 // 調用then方法查看Promise對象的狀態(tài) // 如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù) result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接將普通值傳遞 } }
5 Promise錯誤捕獲
1、捕獲執(zhí)行器錯誤。當執(zhí)行器錯誤時,直接執(zhí)行reject
方法,這個錯誤實際上是在then
方法中的失敗回調函數(shù)中輸出的。
constructor(executor) { // 接收一個執(zhí)行器 try { executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } catch (e) { this.reject(e); // 將錯誤原因傳遞給失敗回調函數(shù) } }
2、捕獲then
方法中回調函數(shù)的錯誤。如果當前then
方法中的回調函數(shù)錯誤,那么應該在下一個then
方法中的失敗回調函數(shù)中輸出。
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(successCallback); this.failCallback.push(failCallback); } }) return promise; }
當狀態(tài)為等待時,也需要為它進行錯誤處理。狀態(tài)為等待時,數(shù)組中原本存入了成功和失敗的回調,但是這樣沒有辦法進行錯誤處理,因此我們可以在數(shù)組中存入一個回調函數(shù),回調函數(shù)內部調用成功或者失敗的函數(shù)。
then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(() => { // 為數(shù)組添加成功回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) }); this.failCallback.push(() => { // 為數(shù)組添加失敗回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) }); } }) return promise; }
那么在resolve
和reject
函數(shù)中,調用成功和失敗函數(shù)的邏輯也需要修改一下:
// 局部代碼 this.successCallback.shift()(); // 調用回調函數(shù) this.failCallback.shift()(); // 調用失敗回調函數(shù)
全部代碼:
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 try { executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } catch (e) { this.reject(e); // 將錯誤原因傳遞給失敗回調函數(shù) } } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = []; // 成功的回調函數(shù) failCallback = []; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 // this.successCallback && this.successCallback(this.value); // 如果成功回調存在,則調用 while (this.successCallback.length) { // 循環(huán)執(zhí)行數(shù)組中的回調函數(shù) this.successCallback.shift()(); // 調用回調函數(shù) } } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 // this.failCallback && this.failCallback(this.reason); // 如果失敗回調存在,則調用 while (this.failCallback.length) { // 循環(huán)執(zhí)行 this.failCallback.shift()(); // 調用失敗回調函數(shù) } } then(successCallback, failCallback) { // then方法有兩個參數(shù) let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(() => { // 為數(shù)組添加成功回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) }); this.failCallback.push(() => { // 為數(shù)組添加失敗回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) }); } }) return promise; } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,報錯 reject(new TypeError("promise對象循環(huán)了")); return; // 阻止代碼向下執(zhí)行 } // 通過判斷result是不是MyPromise的實例對象來判斷是不是Promise對象 if (result instanceof MyPromise) { // 是Promise對象 // 調用then方法查看Promise對象的狀態(tài) // 如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù) result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接將普通值傳遞 } }
6 then方法參數(shù)設置為可選
如果then
方法中沒有參數(shù),那么它的參數(shù)應該傳到之后的then
方法中。
let p = new Promise((resolve, reject) => { resolve(100) }) p.then().then().then(value => console.log(value)); // 100
那么我們可以在then
方法中判斷是否傳入了參數(shù),如果沒有直接傳到下一個then
方法中。
then(successCallback, failCallback) { // then方法有兩個參數(shù) // 如果沒有傳入?yún)?shù),則將值返回 successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback : reason => reason => { throw reason }; let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(() => { // 為數(shù)組添加成功回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) }); this.failCallback.push(() => { // 為數(shù)組添加失敗回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) }); } }) return promise; }
7 實現(xiàn)Promise.all
Promise.all
方法接收一個數(shù)組作為參數(shù),它允許我們按照異步代碼調用的順序得到異步代碼的結果,也就是說,它的輸出結果就是參數(shù)的傳遞順序。這個方法有幾點需要注意:
Promise.all
方法的返回值也是一個Promise對象,也就是說,它也可以鏈式調用then
方法;- 當
Promise.all
中所有結果都是成功的,那么結果就是成功的,如果有一個結果是失敗的,那么它就是失敗的;
Promise.all(["a", "b", p1(), p2(), "c"]).then(result => { // result -> ["a", "b", p1(), p2(), "c"] })
首先,我們看到all
方法是Promise
類名直接調用的,說明它是一個靜態(tài)方法。它接受了一個數(shù)組作為參數(shù),最終返回了一個Promise
對象,那么基本框架我們可以寫出來了:
static all(array) { return new MyPromise((resolve, reject) => { }) }
接著我們應該判斷傳入的參數(shù)是普通值還是Promise對象,如果是普通值,那么直接放到結果數(shù)組中,如果是Promise對象,那么我們就先執(zhí)行這個Promise對象,再將執(zhí)行后的結果放到結果數(shù)組當中。將結果添加到結果數(shù)組中時,我們定義一個函數(shù)addData
來幫助我們。當循環(huán)執(zhí)行完成之后,我們應該調用resolve
方法將result
結果數(shù)組傳遞到外面。
static all(array) { let result = []; // 結果數(shù)組,用來存放結果 function addData(key, value) { // 將結果添加到結果數(shù)組中 result[key] = value; } return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { let current = array[i]; // 獲取當前的值 if (current instanceof MyPromise) { // 是一個Promise對象 // 如果是一個Promise對象,我們首先要執(zhí)行這個對象 // 調用它的then方法,如果成功將結果添加到數(shù)組中,失敗則傳遞錯誤 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一個普通值 addData(i, array[i]); // 普通值直接將結果添加到數(shù)組中 } } resolve(result); // 將結果傳遞出去 }) }
但是這里會出現(xiàn)一個問題,for循環(huán)是一瞬間執(zhí)行完成了,如果Promise中有異步代碼,那么異步代碼并沒有執(zhí)行完就執(zhí)行resolve
方法,那么我們最終拿不到異步任務的結果。所以我們可以設置一個計數(shù)器index
,當結果數(shù)組中有一個結果,就讓計數(shù)器+1,當計數(shù)器的值等于數(shù)組array
的長度時,才能執(zhí)行resolve
方法。
static all(array) { let result = []; // 結果數(shù)組,用來存放結果 let index = 0; // 計數(shù)器,記錄是否執(zhí)行完成 return new MyPromise((resolve, reject) => { // addData方法在數(shù)組中才能執(zhí)行resolve function addData(key, value) { // 將結果添加到結果數(shù)組中 result[key] = value; index++; if (index === array.length) resolve(result); } for (let i = 0; i < array.length; i++) { let current = array[i]; // 獲取當前的值 if (current instanceof MyPromise) { // 是一個Promise對象 // 如果是一個Promise對象,我們首先要執(zhí)行這個對象 // 調用它的then方法,如果成功將結果添加到數(shù)組中,失敗則傳遞錯誤 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一個普通值 addData(i, array[i]); // 普通值直接將結果添加到數(shù)組中 } } }) }
8 實現(xiàn)Promise.resolve
Promise.resolve
方法會將給定的值轉換成Promise對象,也就是說它的返回值就是一個Promise對象。
Promise.resolve(10).then(value => console.log(value));
Promise.resolve
方法是一個靜態(tài)方法,在該方法內部會判斷參數(shù)是否為Promise對象,如果是,則原封不動返回,如果不是,就創(chuàng)建一個Promise對象,將給定的值包裹在Promise對象當中,最后返回即可。
static resolve(value) { if (value instanceof MyPromise) return value; return new MyPromise(resolve => resolve(value)); }
9 實現(xiàn)Promise.race
Promise.race
參數(shù)是數(shù)組,它會返回一個Promise對象,一旦某個參數(shù)先改變狀態(tài),那么直接就將該狀態(tài)返回,也就是說,看誰執(zhí)行的更快。
Promise.race(["a", "b", "c"]).then(value => console.log(value));
Promise.race
方法是一個靜態(tài)方法,它接受一個數(shù)組作為參數(shù),最后返回一個Promise對象,基礎框架如下:
static race(array) { return new MyPromise((reslve, reject) => { }) }
循環(huán)遍歷參數(shù),返回第一個成功或者失敗的結果。
static race(array) { return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { Promise.resolve(array[i]).then(value => resolve(value), reason => reject(reason)); } }) }
10 實現(xiàn)finally方法
finally
方法接受一個回調函數(shù)作為參數(shù),它有一些特點:
- 無論最后Promise對象最終的狀態(tài)是成功的還是失敗的,該方法中回調函數(shù)都會被執(zhí)行一次
finally
方法后可以鏈式調用then
方法拿到當前Promise對象最終返回的結果。
let promise = new Promise(); promise.finally(() => console.log("finally")).then(value => console.log(value));
首先我們要獲得Promise對象的狀態(tài),我們可以調用then
方法來獲得狀態(tài)。由于無論狀態(tài)成功還是失敗,我們都要調用回調函數(shù),因此在then
方法中成功和失敗中都調用一次該回調函數(shù)。由于finally
返回Promise對象,then
方法也返回Promise對象,因此我們直接將then
方法返回即可。
finally(callBack) { return this.then(() => { callBack(); // 成功的回調函數(shù)中調用 }, () => { callBack(); // 失敗的回調函數(shù)中調用 }) }
接著,我們需要在成功的回調函數(shù)中返回成功的值,在失敗的回調函數(shù)中傳遞失敗的原因。
finally(callBack) { return this.then(value => { callBack(); // 成功的回調函數(shù)中調用 return value; // 將成功的值返回 }, reason => { callBack(); // 失敗的回調函數(shù)中調用 throw reason; // 將失敗原因傳遞下去 }) }
如果在finally
后面的then
中返回了一個Promise對象,對象中有異步代碼,那么我們應該等待異步代碼執(zhí)行完成之后,再繼續(xù)執(zhí)行后面的then
方法。所以我們應該判斷callBack
的返回值是什么,如果是一個普通值,我們將其轉換為Promise對象,等待其執(zhí)行完成,如果是一個Promise對象,我們還是等待其執(zhí)行完成。
finally(callBack) { return this.then(value => { return MyPromise.resolve(callBack()).then(() => value); }, reason => { return MyPromise.resolve(callBack()).then(() => { throw reason }); }) }
11 實現(xiàn)catch方法
catch
方法用來處理Promise對象最終為失敗的情況,當我們調用then
方法是,是可以不傳遞失敗回調的,那么失敗回調會被catch
方法所捕獲。
let promise = new Promise(); promise.then(value => console.log(value)).catch(reason => console.log(reason));
catch
方法接受一個回調函數(shù)作為參數(shù),在其內部調用then
方法,并且不傳遞成功回調,只傳遞失敗回調函數(shù),最后將then
方法返回即可。
catch(failCallback) { return this.then(undefined, failCallback); }
12 全部代碼展示
const PENDING = "pending"; // 等待 const FULFILLED = "fulfilled"; // 成功 const REJECTED = "rejected"; // 失敗 class MyPromise { constructor(executor) { // 接收一個執(zhí)行器 try { executor(this.resolve, this.reject); // 執(zhí)行器會立即執(zhí)行 } catch (e) { this.reject(e); // 將錯誤原因傳遞給失敗回調函數(shù) } } status = PENDING; // 狀態(tài)默認為pending等待 value = undefined; // 成功之后的值 reason = undefined; // 失敗之后的原因 successCallback = []; // 成功的回調函數(shù) failCallback = []; // 失敗的回調函數(shù) resolve = value => { // value是成功之后的值 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = FULFILLED; // 將狀態(tài)改為成功 this.value = value; // 將成功的值傳遞 // this.successCallback && this.successCallback(this.value); // 如果成功回調存在,則調用 while (this.successCallback.length) { // 循環(huán)執(zhí)行數(shù)組中的回調函數(shù) this.successCallback.shift()(); // 調用回調函數(shù) } } reject = reason => { // reason是失敗之后的原因 if (this.status !== PENDING) return; // 當狀態(tài)不是等待,直接返回 this.status = REJECTED; // 將狀態(tài)改為失敗 this.reason = reason; // 將失敗的值傳遞 // this.failCallback && this.failCallback(this.reason); // 如果失敗回調存在,則調用 while (this.failCallback.length) { // 循環(huán)執(zhí)行 this.failCallback.shift()(); // 調用失敗回調函數(shù) } } then(successCallback, failCallback) { // then方法有兩個參數(shù) // 如果沒有傳入?yún)?shù),則將值返回 successCallback = successCallback ? successCallback : value => value; failCallback = failCallback ? failCallback : reason => { throw reason }; let promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { // 成功調用第一個回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) } else if (this.status === REJECTED) { // 失敗調用第二個回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) } else { // 當狀態(tài)為等待時,將成功回調和失敗回調存儲起來 this.successCallback.push(() => { // 為數(shù)組添加成功回調函數(shù) setTimeout(() => { // 變成異步代碼,獲取promise try { let result = successCallback(this.value); resolvePromise(promise, result, resolve, reject); // 調用方法 } catch (e) { reject(e); // 將錯誤傳遞到下一個then中 } }, 0) }); this.failCallback.push(() => { // 為數(shù)組添加失敗回調函數(shù) setTimeout(() => { try { let result = failCallback(this.reason); resolvePromise(promise, result, resolve, reject); } catch (e) { reject(e); // 傳遞錯誤 } }, 0) }); } }) return promise; } finally(callBack) { return this.then(value => { return MyPromise.resolve(callBack()).then(() => value); }, reason => { return MyPromise.resolve(callBack()).then(() => { throw reason }); }) } catch(failCallback) { return this.then(undefined, failCallback); } static all(array) { let result = []; // 結果數(shù)組,用來存放結果 let index = 0; // 計數(shù)器,記錄是否執(zhí)行完成 return new MyPromise((resolve, reject) => { function addData(key, value) { // 將結果添加到結果數(shù)組中 result[key] = value; index++; if (index === array.length) resolve(result); } for (let i = 0; i < array.length; i++) { let current = array[i]; // 獲取當前的值 if (current instanceof MyPromise) { // 是一個Promise對象 // 如果是一個Promise對象,我們首先要執(zhí)行這個對象 // 調用它的then方法,如果成功將結果添加到數(shù)組中,失敗則傳遞錯誤 current.then(value => addData(i, value), reason => reject(reason)) } else { // 是一個普通值 addData(i, array[i]); // 普通值直接將結果添加到數(shù)組中 } } }) } static resolve(value) { if (value instanceof MyPromise) return value; return new MyPromise(resolve => resolve(value)); } static race(array) { return new MyPromise((resolve, reject) => { for (let i = 0; i < array.length; i++) { Promise.resolve(array[i]).then(value => resolve(value), reason => reject(reason)); } }) } } function resolvePromise(promise, result, resolve, reject) { if (promise === result) { // 如果相同,報錯 reject(new TypeError("promise對象循環(huán)了")); return; // 阻止代碼向下執(zhí)行 } // 通過判斷result是不是MyPromise的實例對象來判斷是不是Promise對象 if (result instanceof MyPromise) { // 是Promise對象 // 調用then方法查看Promise對象的狀態(tài) // 如果成功調用第一個回調函數(shù),如果失敗調用第二個回調函數(shù) result.then(value => resolve(value), reason => reject(reason)); } else { // 如果是普通值 resolve(result); // 直接將普通值傳遞 } }
到此這篇關于JavaScript Promise原理與實現(xiàn)刨析的文章就介紹到這了,更多相關JS Promise內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
javascript設計模式 – 策略模式原理與用法實例分析
這篇文章主要介紹了javascript設計模式 – 策略模式,結合實例形式分析了javascript策略模式相關概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04JS使用隊列對數(shù)組排列,基數(shù)排序算法示例
這篇文章主要介紹了JS使用隊列對數(shù)組排列,基數(shù)排序算法,涉及javascript隊列的定義、使用,基數(shù)排序實現(xiàn)方法等相關操作技巧,需要的朋友可以參考下2019-03-03微信小程序webview中監(jiān)聽返回按鈕實現(xiàn)步驟
在微信小程序中webview返回鍵是一個非常實用的功能,它允許用戶在嵌入的網(wǎng)頁中返回到上一個頁面,這篇文章主要給大家介紹了微信小程序webview中監(jiān)聽返回按鈕的實現(xiàn)步驟,需要的朋友可以參考下2024-08-08GWT中復制到剪貼板 js+flash實現(xiàn)復制 兼容性比較好
今天看到有個Google Code的項目,叫ZeroClipboard,大意是使用flash作為媒介,將內容復制到剪貼板。這比用純javascript好,因為不同瀏覽器會出于安全的原因,有不同反應,例如IE會給出提示,有的瀏覽器不支持復制到剪貼板。2010-03-03