從零實(shí)現(xiàn)一個簡易的 Promise
所有問題都可以通過加一層中間層來解決。
Promises/A+
簡易的,不做廢話直接開始 :)
const p = new Promise((resolve, reject)=>{ // 如果操作成功則調(diào)用 resolve 并傳入 value // 如果操作失敗則調(diào)用 reject 并傳入 reason });
通常我們都會使用上述方法獲取 Promise 實(shí)例:在構(gòu)造函數(shù)種傳入一個 executor 方法,當(dāng)同步/異步的任務(wù)完成時調(diào)用 resolve,失敗時調(diào)用 reject,簡單易懂,在此不多贅述。
狀態(tài)機(jī)一個 Promise 可以理解為一個狀態(tài)機(jī),相應(yīng)的 API 接口要么用于改變狀態(tài)機(jī)的狀態(tài),要么在到達(dá)某個狀態(tài)時被觸發(fā),因此首先需要實(shí)現(xiàn)的是 Promise 的狀態(tài)信息:
const PENDING = 0 const FULFILLED = 1 const REJECTED = 2
并且只存在 PENDING => FULFILLED 或者 PENDING => REJECTED 的狀態(tài)轉(zhuǎn)移。
構(gòu)造函數(shù)首先實(shí)現(xiàn)構(gòu)造函數(shù)的框架如下:
class Promise { constructor(executor) { this.status = PENDING; // 實(shí)例當(dāng)前的狀態(tài) this.data = undefined; // Promise 返回的值 this.defered = []; // 回調(diào)函數(shù)集 executor(resolve, reject); // 執(zhí)行 executor 并傳入相應(yīng)的參數(shù) } }
上述代碼基本實(shí)現(xiàn) Promise 構(gòu)造函數(shù)的主題部分,但是存在三個問題:
resolve 和 reject 參數(shù)/方法尚未定義
executor 函數(shù)體中可能會拋出異常,需要做容錯處理
考慮 executor 函數(shù)體中在調(diào)用 resolve/reject 時的 this 指向問題
修修補(bǔ)補(bǔ)如下:
class Promise { constructor(executor) { this.status = PENDING; this.data = undefined; this.defered = []; try { // bind, bind, bind! executor(this.resolve.bind(this), this.reject.bind(this)); } catch (e) { this.reject(e); } } resolve(value) { // TODO } reject(reason) { // TODO } }resolve & reject
接下來實(shí)現(xiàn) resolve 和 reject 方法,基本上就是在判斷狀態(tài)為 PENDING 之后把狀態(tài)改為相應(yīng)的值,并把對應(yīng)的 value 和 reason 存在 self 的 data 屬性上面,最后執(zhí)行相應(yīng)的回調(diào)函數(shù),邏輯很簡單:
resolve(value) { if (this.status === PENDING) { this.status = FULFILLED; this.data = value; this.defered.forEach(i => i.onfulfiled(value)); } } reject(reason) { if (this.status === PENDING) { this.status = REJECTED; this.data = reason; this.defered.forEach(i => i.onrejected(reason)); } }then 方法
Promise 對象有一個 then 方法,用來注冊在這個 Promise 狀態(tài)確定后的回調(diào),很明顯 then 方法需要寫在原型鏈上,Promise 總共有三種可能的狀態(tài),在 then 方法中我們分別用三個判斷分支來處理,并且都分別返回一個新的 Promise 實(shí)例。
then(onResolved, onRejected) { // 如果 then 的參數(shù)不是 function 則我們需要忽略它 onResolved = typeof onResolved === "function" ? onResolved : function(v) {}; onRejected = typeof onRejected === "function" ? onRejected : function(r) {}; switch (this.status) { case FULFILLED: return new Promise((resolve, reject) => { // TODO }); case REJECTED: return new Promise((resolve, reject) => { // TODO }); case PENDING: return new Promise((resolve, reject) => { // TODO }); } }
完整的實(shí)現(xiàn)如下,其中需要注意的是,如果 onResolved 的返回值是一個 Promise 對象,則直接取它的結(jié)果做為新的 Promise 實(shí)例的結(jié)果:
then(onResolved, onRejected) { onResolved = typeof onResolved === "function" ? onResolved : function(v) {}; onRejected = typeof onRejected === "function" ? onRejected : function(r) {}; switch (this.status) { case FULFILLED: return new Promise((resolve, reject) => { try { const r = onResolved(this.data); r instanceof Promise && r.then(resolve, reject); resolve(r); } catch (e) { reject(e); } }); case REJECTED: return new Promise((resolve, reject) => { try { const r = onRejected(this.data); r instanceof Promise && r.then(resolve, reject); } catch (e) { reject(e); } }); case PENDING: return new Promise((resolve, reject) => { const onfulfiled = () => { try { const r = onResolved(this.data); r instanceof Promise && r.then(resolve, reject); } catch (e) { reject(e); } }; const onrejected = () => { try { const r = onRejected(this.data); r instanceof Promise && r.then(resolve, reject); } catch (e) { reject(e); } }; this.defered.push({ onfulfiled, onrejected }); }); } }
至此實(shí)現(xiàn)一個簡易的 Promise,使用如下測試用例驗(yàn)證:
new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }).then((res) => { console.log(res); return new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000); }); }).then((res) => { console.log(res); return new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 1000); }); }).then((res) => { console.log(res); }); // 1 // 2 // 3 // [Finished in 3.1s]其他問題(UPDATED) 異步問題
new Promise((resolve) => { resolve(); }) .then(() => { console.log("1"); }) .then(() => { console.log("2"); }); console.log("3");
執(zhí)行上面的代碼會發(fā)現(xiàn)輸出的順序是“1, 2, 3”,而不是正確的“3, 1, 2”,顯然是因?yàn)槲覀儧]有在 Promise 的 resolve 方法中異步的調(diào)用回調(diào)函數(shù)集導(dǎo)致的,當(dāng)然解決這個問題也很簡單,就是使用 setTimeout,但是這樣實(shí)現(xiàn)的話并不符合 Promise 在事件循環(huán)中的優(yōu)先級,所以暫時忽略。
值穿透問題new Promise((resolve) => { resolve(8); }) .then() .then() .then((value) => { console.log(value) });
上面的代碼使用我們剛剛實(shí)現(xiàn)的 Promise 會打印 undefined,然而這并不是我們期望得到的結(jié)果,我們希望的是8這個值會穿過兩個 then 到達(dá)鏈尾的 then 的執(zhí)行函數(shù)里,其輸出應(yīng)該和這段代碼一致:
new Promise((resolve) => { resolve(8); }) .then((value) => { return value; }) .then((value) => { return value; }) .then((value) => { console.log(value); });
其實(shí)要實(shí)現(xiàn)這個功能十分簡單,只要把 then 的兩個參數(shù)的默認(rèn)值做簡單的修改:
onResolved = typeof onResolved === "function" ? onResolved : function(v) { return v; }; onRejected = typeof onRejected === "function" ? onRejected : function(r) { return r; };Promise 就是充當(dāng)一個中間層,把回調(diào)造成的控制反轉(zhuǎn)再反轉(zhuǎn)回去。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/94843.html
摘要:不過仔細(xì)了解了一段時候發(fā)現(xiàn),其實(shí)他的原理是很簡單的,所以想要自己也動手實(shí)現(xiàn)一個功能類似的框架。原文地址從零開始實(shí)現(xiàn)一個簡易的框架 前言 最近在看spring-boot框架的源碼,看了源碼之后更是讓我感受到了spring-boot功能的強(qiáng)大。而且使用了很多的設(shè)計模式,讓人在看的時候覺得有點(diǎn)難以下手。 不過仔細(xì)了解了一段時候發(fā)現(xiàn),其實(shí)他的原理是很簡單的,所以想要自己也動手實(shí)現(xiàn)一個功能類似的...
摘要:接下來就可以把這個切點(diǎn)類加入到我們之前實(shí)現(xiàn)的功能中了。實(shí)現(xiàn)的切點(diǎn)功能首先改裝注解,把之前改成來存儲表達(dá)式。測試用例在上一篇文章從零開始實(shí)現(xiàn)一個簡易的框架四實(shí)現(xiàn)中的測試用例的基礎(chǔ)上修改測試用例。 前言 在上一節(jié)從零開始實(shí)現(xiàn)一個簡易的Java MVC框架(四)--實(shí)現(xiàn)AOP中我們實(shí)現(xiàn)了AOP的功能,已經(jīng)可以生成對應(yīng)的代理類了,但是對于代理對象的選擇只能通過指定的類,這樣確實(shí)不方便也不合理。...
摘要:在前面的文章中實(shí)現(xiàn)的功能時,目標(biāo)類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。改裝原有功能現(xiàn)在要改裝原來的的實(shí)現(xiàn)代碼,讓的功能加入到框架中為了讓切面能夠排序,先添加一個注解,用于標(biāo)記排序。 前言 在前面從零開始實(shí)現(xiàn)一個簡易的Java MVC框架(四)--實(shí)現(xiàn)AOP和從零開始實(shí)現(xiàn)一個簡易的Java MVC框架(五)--引入aspectj實(shí)現(xiàn)AOP切點(diǎn)這兩節(jié)文章...
摘要:服務(wù)器相關(guān)配置啟動類資源目錄目錄靜態(tài)文件目錄端口號目錄目錄實(shí)現(xiàn)內(nèi)嵌服務(wù)器在上一章文章從零開始實(shí)現(xiàn)一個簡易的框架七實(shí)現(xiàn)已經(jīng)在文件中引入了依賴,所以這里就不用引用了。 spring-boot的Starter 一個項(xiàng)目總是要有一個啟動的地方,當(dāng)項(xiàng)目部署在tomcat中的時候,經(jīng)常就會用tomcat的startup.sh(startup.bat)的啟動腳本來啟動web項(xiàng)目 而在spring-b...
摘要:前言在從零開始實(shí)現(xiàn)一個簡易的框架七實(shí)現(xiàn)中實(shí)現(xiàn)了框架的的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 前言 在從零開始實(shí)現(xiàn)一個簡易的Java MVC框架(七)--實(shí)現(xiàn)MVC中實(shí)現(xiàn)了doodle框架的MVC的功能,不過最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 優(yōu)化的目標(biāo)是1.去除DispatcherServlet請求分發(fā)器中的http邏...
閱讀 3390·2023-04-26 01:40
閱讀 3093·2021-11-24 09:39
閱讀 1402·2021-10-27 14:19
閱讀 2648·2021-10-12 10:11
閱讀 1307·2021-09-26 09:47
閱讀 1847·2021-09-22 15:21
閱讀 2711·2021-09-06 15:00
閱讀 894·2021-08-10 09:44