摘要:通過或者拿到方法回調(diào)函數(shù)的返回值,然后調(diào)用,將新增的的和傳入到中。打印結(jié)果實現(xiàn)方法接收一個包含多個的數(shù)組,當有一個為狀態(tài)時,整個大的為,并執(zhí)行回調(diào)函數(shù)。
前言
Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對于這些不熟悉的可以參考我另一篇文章《JavaScript異步編程》),這不僅僅是技術(shù)實現(xiàn)的發(fā)展,更是思想上對于如何控制異步的遞進。Promise作為后續(xù)方案的基礎(chǔ),是重中之重,也是面試時候最常被問到的。
今天我們就一起從0到1實現(xiàn)一個基于A+規(guī)范的Promise,過程中也會對Promise的異常處理,以及是否可手動終止做一些討論,最后會對我們實現(xiàn)的Promise做單元測試。完整的代碼已經(jīng)上傳到github,想直接看代碼的可以點這里。
雖然已經(jīng)有很多帶你實現(xiàn)Promise類的文章了,但每個人理解的程度不一樣,也許不同的文章可以帶給你不同的思考呢,那我們就開始吧。
正文 1. 基礎(chǔ)框架new Promise()時接收一個executor函數(shù)作為參數(shù),該函數(shù)會立即執(zhí)行,函數(shù)中有兩個參數(shù),它們也是函數(shù),分別是resolve和reject,函數(shù)同步執(zhí)行一定要放在try...catch中,否則無法進行錯誤捕獲。
MyPromise.js
function MyPromise(executor) { function resolve(value) { } function reject(reason) { } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise;
resolve()接收Promise成功值value,reject接收Promise失敗原因reason。
test.js
let MyPromise = require("./MyPromise.js"); let promise = new MyPromise(function(resolve, reject) { resolve(123); })2. 添加狀態(tài)機
目前實現(xiàn)存在的問題:
Promise是一個狀態(tài)機的機制,初始狀態(tài)為 pending,成功狀態(tài)為 fulfilled,失敗狀態(tài)為 rejected。只能從 pending -> fulfilled,或者從 pending -> rejected,并且狀態(tài)一旦轉(zhuǎn)變,就永遠不會再變了。
所以,我們需要為Promise添加一個狀態(tài)流轉(zhuǎn)的機制。
MyPromise.js
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function MyPromise(executor) { let self = this; self.state = PENDING; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise;
test.js
let MyPromise = require("./MyPromise.js"); let promise = new MyPromise(function(resolve, reject) { resolve(123); }); promise.then(function(value) { console.log("value", value); }, function(reason) { console.log("reason", reason); })3. 添加then方法
Promise擁有一個then方法,接收兩個函數(shù) onFulfilled 和 onRejected,分別作為Promise成功和失敗的回調(diào)。所以,在then方法中我們需要對狀態(tài)state進行判斷,如果是fulfilled,則執(zhí)行onFulfilled(value)方法,如果是rejected,則執(zhí)行onRejected(reason)方法。
由于成功值value和失敗原因reason是由用戶在executor中通過resolve(value) 和 reject(reason)傳入的,所以我們需要有一個全局的value和reason供后續(xù)方法獲取。
MyPromise.js
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise;4. 實現(xiàn)異步調(diào)用resolve
目前實現(xiàn)存在的問題:
同步調(diào)用resolve()沒有問題,但如果是異步調(diào)用,比如放到setTimeout中,因為目前的代碼在調(diào)用then()方法時,state仍是pending狀態(tài),當timer到時候調(diào)用resolve()把state修改為fulfilled狀態(tài),但是onFulfilled()函數(shù)已經(jīng)沒有時機調(diào)用了。
針對上述問題,進行如下修改:
MyPromise.js
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; self.onFulfilledCallbacks = []; self.onRejectedCallbacks = []; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; self.onFulfilledCallbacks.forEach(function(fulfilledCallback) { fulfilledCallback(); }); } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; self.onRejectedCallbacks.forEach(function(rejectedCallback) { rejectedCallback(); }); } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { onFuifilled(self.value); }); self.onRejectedCallbacks.push(() => { onRejected(self.reason); }); } if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise;
我們添加了兩個回調(diào)函數(shù)數(shù)組onFulfilledCallbacks和onRejectedCallbacks,用來存儲then()方法中傳入的成功和失敗回調(diào)。然后,當用戶調(diào)用resolve()或reject()的時候,修改state狀態(tài),并從相應(yīng)的回調(diào)數(shù)組中依次取出回調(diào)函數(shù)執(zhí)行。
同時,通過這種方式我們也實現(xiàn)了可以注冊多個then()函數(shù),并且在成功或者失敗時按照注冊順序依次執(zhí)行。
test.js
let MyPromise = require("./MyPromise.js"); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then(function(value) { console.log("value1", value); }, function(reason) { console.log("reason1", reason); }); promise.then(function(value) { console.log("value2", value); }, function(reason) { console.log("reason2", reason); });5. then返回的仍是Promise
讀過PromiseA+規(guī)范的同學肯定知道,then()方法返回的仍是一個Promise,并且返回Promise的resolve的值是上一個Promise的onFulfilled()函數(shù)或onRejected()函數(shù)的返回值。如果在上一個Promise的then()方法回調(diào)函數(shù)的執(zhí)行過程中發(fā)生了錯誤,那么會將其捕獲到,并作為返回的Promise的onRejected函數(shù)的參數(shù)傳入。比如:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log("value1", value); return 456; }).then((value) => { console.log("value2", value); }); let promise = new Promise((resolve, reject) => { resolve(123); });
打印結(jié)果為:
value1 123
value2 456
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log("value1", value); a.b = 2; // 這里存在語法錯誤 return 456; }).then((value) => { console.log("value2", value); }, (reason) => { console.log("reason2", reason); });
打印結(jié)果為:
value1 123
reason2 ReferenceError: a is not defined
可以看到,then()方法回調(diào)函數(shù)如果發(fā)生錯誤,會被捕獲到,那么then()返回的Promise會自動變?yōu)?b>onRejected,執(zhí)行onRejected()回調(diào)函數(shù)。
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log("value1", value); return 456; }, (reason) => { console.log("reason1", reason); return 456; }).then((value) => { console.log("value2", value); }, (reason) => { console.log("reason2", reason); });
打印結(jié)果為:
reason1 123
value2 456
好啦,接下來我們就去實現(xiàn)then()方法依然返回一個Promise。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); self.onRejectedCallbacks.push(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); } if (self.state === FULFILLED) { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } if (self.state === REJECTED) { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } }); return promise2; };
可以看到,我們新增了一個promise2作為then()方法的返回值。通過let x = onFuifilled(self.value) 或者 let x = onRejected(self.reason)拿到then()方法回調(diào)函數(shù)的返回值,然后調(diào)用self.resolvePromise(promise2, x, resolve, reject),將新增的promise2、x、promise2的resolve和reject傳入到resolvePromise()中。
所以,下面我們重點看一下resolvePromise()方法。
MyPromise.js
MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) { let self = this; let called = false; // called 防止多次調(diào)用 if (promise2 === x) { return reject(new TypeError("循環(huán)引用")); } if (x !== null && (Object.prototype.toString.call(x) === "[object Object]" || Object.prototype.toString.call(x) === "[object Function]")) { // x是對象或者函數(shù) try { let then = x.then; if (typeof then === "function") { then.call(x, (y) => { // 別人的Promise的then方法可能設(shè)置了getter等,使用called防止多次調(diào)用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); } };
resolvePromise()是用來解析then()回調(diào)函數(shù)中返回的仍是一個Promise,這個Promise有可能是我們自己的,有可能是別的庫實現(xiàn)的,也有可能是一個具有then()方法的對象,所以這里靠resolvePromise()來實現(xiàn)統(tǒng)一處理。
下面是翻譯自PromiseA+規(guī)范關(guān)于resolvePromise()的要求:
Promise 解決過程
Promise 解決過程是一個抽象的操作,其需輸入一個 promise 和一個值,我們表示為 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態(tài);否則其用 x 的值來執(zhí)行 promise 。
這種 thenable 的特性使得 Promise 的實現(xiàn)更具有通用性:只要其暴露出一個遵循 Promise/A+ 協(xié)議的 then 方法即可;這同時也使遵循 Promise/A+ 規(guī)范的實現(xiàn)可以與那些不太規(guī)范但可用的實現(xiàn)能良好共存。
運行 [[Resolve]](promise, x) 需遵循以下步驟:
x 與 promise 相等
如果 promise 和 x 指向同一對象,以 TypeError 為據(jù)因拒絕執(zhí)行 promise
x 為 Promise
如果 x 為 Promise ,則使 promise 接受 x 的狀態(tài):
- 如果 x 處于等待態(tài), promise 需保持為等待態(tài)直至 x 被執(zhí)行或拒絕 - 如果 x 處于執(zhí)行態(tài),用相同的值執(zhí)行 promise - 如果 x 處于拒絕態(tài),用相同的據(jù)因拒絕 promise
x 為對象或函數(shù)
如果 x 為對象或者函數(shù):
- 把 x.then 賦值給 then - 如果取 x.then 的值時拋出錯誤 e ,則以 e 為據(jù)因拒絕 promise - 如果 then 是函數(shù),將 x 作為函數(shù)的作用域 this 調(diào)用之。傳遞兩個回調(diào)函數(shù)作為參數(shù),第一個參數(shù)叫做 resolvePromise ,第二個參數(shù)叫做 rejectPromise: - 如果 resolvePromise 以值 y 為參數(shù)被調(diào)用,則運行 [[Resolve]](promise, y) - 如果 rejectPromise 以據(jù)因 r 為參數(shù)被調(diào)用,則以據(jù)因 r 拒絕 promise - 如果 resolvePromise 和 rejectPromise 均被調(diào)用,或者被同一參數(shù)調(diào)用了多次,則優(yōu)先采用首次調(diào)用并忽略剩下的調(diào)用 - 如果調(diào)用 then 方法拋出了異常 e: - 如果 resolvePromise 或 rejectPromise 已經(jīng)被調(diào)用,則忽略之 - 否則以 e 為據(jù)因拒絕 promise - 如果 then 不是函數(shù),以 x 為參數(shù)執(zhí)行 promise - 如果 x 不為對象或者函數(shù),以 x 為參數(shù)執(zhí)行 promise
如果一個 promise 被一個循環(huán)的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質(zhì)又使得其被再次調(diào)用,根據(jù)上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 為據(jù)因來拒絕 promise。
參考上述規(guī)范,結(jié)合代碼中的注釋,相信大家可以理解resolvePromise()的作用了。
測試:
test.js
let MyPromise = require("./MyPromise.js"); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then((value) => { console.log("value1", value); return new MyPromise((resolve, reject) => { resolve(456); }).then((value) => { return new MyPromise((resolve, reject) => { resolve(789); }) }); }, (reason) => { console.log("reason1", reason); }).then((value) => { console.log("value2", value); }, (reason) => { console.log("reason2", reason); });
打印結(jié)果:
value1 1236. 讓then()方法的回調(diào)函數(shù)總是異步調(diào)用
value2 789
官方Promise實現(xiàn)的回調(diào)函數(shù)總是異步調(diào)用的:
console.log("start"); let promise = new Promise((resolve, reject) => { console.log("step-"); resolve(123); }); promise.then((value) => { console.log("step--"); console.log("value", value); }); console.log("end");
打印結(jié)果:
start
step-
end
step--
value1 123
Promise屬于微任務(wù),這里我們?yōu)榱朔奖阌煤耆蝿?wù)setTiemout來代替實現(xiàn)異步,具體關(guān)于宏任務(wù)、微任務(wù)以及Event Loop可以參考我的另一篇文章帶你徹底弄懂Event Loop。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFuifilled(self.value); 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) { setTimeout(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } if (self.state === REJECTED) { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } }); return promise2; };
測試:
test.js
let MyPromise = require("./MyPromise.js"); console.log("start"); let promise = new MyPromise((resolve, reject) => { console.log("step-"); setTimeout(() => { resolve(123); }, 1000); }); promise.then((value) => { console.log("step--"); console.log("value", value); }); console.log("end");
打印結(jié)果:
start
step-
end
step--
value1 123
經(jīng)過以上步驟,一個最基本的Promise就已經(jīng)實現(xiàn)完了,下面我們會實現(xiàn)一些不在PromiseA+規(guī)范的擴展方法。
7. 實現(xiàn)catch()方法then()方法的onFulfilled和onRejected回調(diào)函數(shù)都不是必傳項,如果不傳,那么我們就無法接收reject(reason)中的錯誤,這時我們可以通過鏈式調(diào)用catch()方法用來接收錯誤。舉例:
let promise = new Promise((resolve, reject) => { reject("has error"); }); promise.then((value) => { console.log("value", value); }).catch((reason) => { console.log("reason", reason); });
打印結(jié)果:
reason has error
不僅如此,catch()可以作為Promise鏈式調(diào)用的最后一步,前面Promise發(fā)生的錯誤會冒泡到最后一個catch()中,從而捕獲異常。舉例:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log("value", value); return new Promise((resolve, reject) => { reject("has error1"); }); }).then((value) => { console.log("value", value); return new Promise((resolve, reject) => { reject("has error2"); }); }).catch((reason) => { console.log("reason", reason); });
打印結(jié)果:
value 123
reason has error1
那么catch()方法到底是如何實現(xiàn)的呢?
答案就是在Promise的實現(xiàn)中,onFulfilled和onRejected函數(shù)是有默認值的:
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { onFuifilled = typeof onFuifilled === "function" ? onFuifilled : value => {return value;}; onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}; }; MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); };
可以看到,onRejected的默認值是把錯誤reason通過throw拋出去。由于我們對于同步代碼的執(zhí)行都是在try...catch中的,所以如果Promise發(fā)生了錯誤,如果沒傳onRejected,默認的函數(shù)會把錯誤reason拋出,然后會被promise2捕捉到,作為reject(reason)決議。
catch()實現(xiàn)就是調(diào)用this.then(null, onRejected),由于promise2被reject,所以會執(zhí)行onRejected回調(diào),于是就捕捉到了第一個promise的錯誤。
總結(jié)來說,then()方法中不傳onRejected回調(diào),Promise內(nèi)部會默認幫你寫一個函數(shù)作為回調(diào),作用就是throw拋出reject或者try...catch到的錯誤,然后錯誤reason會被promise2作為reject(reason)進行決議,于是會被下一個then()方法的onRejected回調(diào)函數(shù)調(diào)用,而catch只是寫了一個特殊的then(null, onRejected)而已。
所以,我們在寫Promise的鏈式調(diào)用的時候,在then()中可以不傳onRejected回調(diào),只需要在鏈式調(diào)用的最末尾加一個catch()就可以了,這樣在該鏈條中的Promise發(fā)生的錯誤都會被最后的catch捕獲到。
舉例1:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { // 注意,不會走這里,因為第一個promise是被reject的 console.log("value1", value); return new Promise((resolve, reject) => { reject("has error1"); }); }).then((value) => { console.log("value2", value); return new Promise((resolve, reject) => { reject("has error2"); }); }, (reason) => { // 注意,這個then有onRejected回調(diào) console.log("reason2", reason); }).catch((reason) => { // 錯誤在上一個then就被捕獲了,所以不會走到這里 console.log("reason3", reason); });
打印結(jié)果:
reason2 123
舉例2:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log("value1", value); return new Promise((resolve, reject) => { reject("has error1"); }); }).then((value) => { console.log("value2", value); return new Promise((resolve, reject) => { reject("has error2"); }); }).catch((reason) => { // 由于鏈條中的then都沒有onRejected回調(diào),所以會一直被冒泡到最后的catch這里 console.log("reason3", reason); });
catch和then一樣都是返回一個新的Promise。有的同學可能會有疑問,如果catch中的回調(diào)執(zhí)行也發(fā)生錯誤該怎么辦呢,這個我們后續(xù)在Promise異常處理中再做討論。
打印結(jié)果:
reason3 1238. 實現(xiàn)finally方法
finally是某些庫對Promise實現(xiàn)的一個擴展方法,無論是resolve還是reject,都會走finally方法。
MyPromise.js
MyPromise.prototype.finally = function(fn) { return this.then(value => { fn(); return value; }, reason => { fn(); throw reason; }); };9. 實現(xiàn)done方法
done方法作為Promise鏈式調(diào)用的最后一步,用來向全局拋出沒有被Promise內(nèi)部捕獲的錯誤,并且不再返回一個Promise。一般用來結(jié)束一個Promise鏈。
MyPromise.js
MyPromise.prototype.done = function() { this.catch(reason => { console.log("done", reason); throw reason; }); };10. 實現(xiàn)Promise.all方法
Promise.all()接收一個包含多個Promise的數(shù)組,當所有Promise均為fulfilled狀態(tài)時,返回一個結(jié)果數(shù)組,數(shù)組中結(jié)果的順序和傳入的Promise順序一一對應(yīng)。如果有一個Promise為rejected狀態(tài),則整個Promise.all為rejected。
MyPromise.js
MyPromise.all = function(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); }); }); };
test.js
let MyPromise = require("./MyPromise.js"); let promise1 = new MyPromise((resolve, reject) => { console.log("aaaa"); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log("bbbb"); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log("cccc"); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.all([promise1, promise2, promise3]).then((value) => { console.log("all value", value); }, (reason) => { console.log("all reason", reason); })
打印結(jié)果:
aaaa11. 實現(xiàn)Promise.race方法
bbbb
cccc
1111
2222
all reason 2222
3333
Promise.race()接收一個包含多個Promise的數(shù)組,當有一個Promise為fulfilled狀態(tài)時,整個大的Promise為onfulfilled,并執(zhí)行onFulfilled回調(diào)函數(shù)。如果有一個Promise為rejected狀態(tài),則整個Promise.race為rejected。
MyPromise.js
MyPromise.race = function(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(promise => { promise.then((value) => { resolve(value); }, reject); }); }); };
test.js
let MyPromise = require("./MyPromise.js"); let promise1 = new MyPromise((resolve, reject) => { console.log("aaaa"); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log("bbbb"); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log("cccc"); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.race([promise1, promise2, promise3]).then((value) => { console.log("all value", value); }, (reason) => { console.log("all reason", reason); })
打印結(jié)果:
aaaa12. 實現(xiàn)Promise.resolve方法
bbbb
cccc
1111
all reason 1111
2222
3333
Promise.resolve用來生成一個fulfilled完成態(tài)的Promise,一般放在整個Promise鏈的開頭,用來開始一個Promise鏈。
MyPromise.js
MyPromise.resolve = function(value) { let promise; promise = new MyPromise((resolve, reject) => { this.prototype.resolvePromise(promise, value, resolve, reject); }); return promise; };
test.js
let MyPromise = require("./MyPromise.js"); MyPromise.resolve(1111).then((value) => { console.log("value1", value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log("value2", value); })
打印結(jié)果:
value1 1111
value2 2222
由于傳入的value有可能是普通值,有可能是thenable,也有可能是另一個Promise,所以調(diào)用resolvePromise進行解析。
12. 實現(xiàn)Promise.reject方法Promise.reject用來生成一個rejected失敗態(tài)的Promise。
MyPromise.js
MyPromise.reject = function(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); };
test.js
let MyPromise = require("./MyPromise.js"); MyPromise.reject(1111).then((value) => { console.log("value1", value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log("value2", value); }).catch(reason => { console.log("reason", reason); });
打印結(jié)果:
reason 111113. 實現(xiàn)Promise.deferred方法
Promise.deferred可以用來延遲執(zhí)行resolve和reject。
MyPromise.js
MyPromise.deferred = function() { let dfd = {}; dfd.promies = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.rfeject = reject; }); return dfd; };
這樣,你就可以在外部通過調(diào)用dfd.resolve()和dfd.reject()來決議該Promise。
13. 如何停止一個Promise鏈假設(shè)這樣一個場景,我們有一個很長的Promise鏈式調(diào)用,這些Promise是依次依賴的關(guān)系,如果鏈條中的某個Promise出錯了,就不需要再向下執(zhí)行了,默認情況下,我們是無法實現(xiàn)這個需求的,因為Promise無論是then還是catch都會返回一個Promise,都會繼續(xù)向下執(zhí)行then或catch。舉例:
new Promise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" }).catch() .then() .then() .catch() .then()
有沒有辦法讓這個鏈式調(diào)用在ERROR!!!的后面就停掉,完全不去執(zhí)行鏈式調(diào)用后面所有回調(diào)函數(shù)呢?
我們自己封裝一個Promise.stop方法。
MyPromise.js
MyPromise.stop = function() { return new Promise(function() {}); };
stop中返回一個永遠不執(zhí)行resolve或者reject的Promise,那么這個Promise永遠處于pending狀態(tài),所以永遠也不會向下執(zhí)行then或catch了。這樣我們就停止了一個Promise鏈。
new MyPromise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" MyPromise.stop(); }).catch() .then() .then() .catch() .then()
但是這樣會有一個缺點,就是鏈式調(diào)用后面的所有回調(diào)函數(shù)都無法被垃圾回收器回收。
14. 如何解決Promise鏈上返回的最后一個Promise出現(xiàn)錯誤看如下例子:
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; });
這里a不存在,所以給a.b賦值是一個語法錯誤,onFulfilled回調(diào)函數(shù)是包在try...catch中執(zhí)行的,錯誤會被catch到,但是由于后面沒有then或catch了,這個錯誤無法被處理,就會被Promise吃掉,沒有任何異常,這就是常說的Promise有可能會吃掉錯誤。
那么我們怎么處理這種情況呢?
方法一
就是我們前面已經(jīng)實現(xiàn)過的done()。
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; }).done();
done()方法相當于一個catch,但是卻不再返回Promise了,注意done()方法中不能出現(xiàn)語法錯誤,否則又無法捕獲了。
方法二
普通錯誤監(jiān)聽window的error事件可以實現(xiàn)捕獲
window.addEventListener("error", error => { console.log(error); // 不會觸發(fā) });
Promise沒有被onRejected()處理的錯誤需要監(jiān)聽unhandledrejection事件
window.addEventListener("unhandledrejection", error => { console.log("unhandledrejection", error); // 可以觸發(fā),而且還可以直接拿到 promise 對象 });14. 單元測試 結(jié)束
相關(guān)單元測試以及完整代碼可以到我的github查看,如果對你有幫助的話,就來個star吧~
歡迎關(guān)注我的公眾號 參考文檔PromiseA+規(guī)范
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98050.html
摘要:,表示當前中的方法的第一個回調(diào)函數(shù)。在函數(shù)執(zhí)行時,我們會創(chuàng)建一個新的,然后將傳入的兩個回調(diào)函數(shù)用新的的屬性保存下來。首先我們需要先創(chuàng)建一個新的用于返回,保證后面用戶調(diào)用函數(shù)進行后續(xù)邏輯處理時可以設(shè)置新的和這兩個回調(diào)函數(shù)。 概述 從上次更新Promise/A+規(guī)范后,已經(jīng)很久沒有更新博客了。之前由于業(yè)務(wù)需要,完成了一個TypeScript語言的Promise庫。這次我們來和大家一步一步介...
摘要:本文僅限瀏覽器環(huán)境測試,環(huán)境可能會不一致狀態(tài)一個實例只能處于三種狀態(tài)中的一種。每次創(chuàng)建的實例都會處于狀態(tài),并且只能由變?yōu)榛驙顟B(tài)。可以認為在實現(xiàn)里與中的都為解決程序。 前言 Promise作為ES6極為重要的一個特性,將我們從無限的回調(diào)地獄中解脫出來,變?yōu)殒準降木帉懟卣{(diào),大大提高的代碼的可讀性。 使用Promise是極為簡單的,但只停留在會使用階段還是會讓我們不知不覺踩到一些坑的。本文會...
摘要:規(guī)范鏈接規(guī)范中文鏈接本篇文章將從的使用角度來剖析源碼具體實現(xiàn)。但是對于則不一樣,回調(diào)函數(shù)通過遞歸調(diào)用自己從而保證其值不為類型才結(jié)束,并將賦值到數(shù)組,最后直到所有的數(shù)組都處理完畢由統(tǒng)一的方法結(jié)束當前的操作,進入處理流程。 開篇 最近在 github 上看到了一個 extremely lightweight Promise polyfill 實現(xiàn),打開源碼發(fā)現(xiàn)只有240行,果然極其輕量級,...
摘要:說白了,就是給聲明的添加一個包含空的對象,再由函數(shù)返回這個空。如此構(gòu)成一個層層包裹的鏈。四首先本質(zhì)是一個遞歸函數(shù),結(jié)束條件是,即終止到未掛載對象的子為止。可以看到這個函數(shù)的作用就是根據(jù)屬性逐個觸發(fā)鏈中的或函數(shù)。 背景 Promise是一種非常實用的異步編程方案,本文對于Promise源碼的分析可以增進讀者對于Promise原理的理解,以后不再害怕異步編程,不用擔心callback he...
摘要:在和方法執(zhí)行的時候訂閱事件,將自己的回調(diào)函數(shù)綁定到事件上,屬性是發(fā)布者,一旦它的值發(fā)生改變就發(fā)布事件,執(zhí)行回調(diào)函數(shù)。實現(xiàn)和方法的回調(diào)函數(shù)都是,當滿足條件對象狀態(tài)改變時,這些回調(diào)會被放入隊列。所以我需要在某個變?yōu)闀r,刪除它們綁定的回調(diào)函數(shù)。 前言 按照文檔說明簡單地實現(xiàn) ES6 Promise的各個方法并不難,但是Promise的一些特殊需求實現(xiàn)起來并不簡單,我首先提出一些不好實現(xiàn)或者容...
閱讀 3433·2021-11-22 09:34
閱讀 1899·2019-08-30 12:53
閱讀 3490·2019-08-28 18:07
閱讀 2977·2019-08-27 10:55
閱讀 2959·2019-08-26 10:12
閱讀 3584·2019-08-23 18:21
閱讀 1338·2019-08-23 14:10
閱讀 1469·2019-08-23 13:04