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