摘要:構造函數的實現我們在使用的時候其實是使用關鍵字創建了一個的實例,其實是一個類,即構造函數,下面來實現構造函數。
Promise 是 js 異步編程的一種解決方案,避免了 “回調地獄” 給編程帶來的麻煩,在 ES6 中成為了標準,這篇文章重點不是敘述 Promise 的基本用法,而是從零開始,手寫一版符合 Promise/A+ 規范的 Promise,如果想了解更多 Promise 的基本用法,可以看 異步發展流程 —— Promise 的基本使用 這篇文章。
我們在使用 Promise 的時候其實是使用 new 關鍵字創建了一個 Promise 的實例,其實 Promise 是一個類,即構造函數,下面來實現 Promise 構造函數。
Promise/A+ 規范的內容比較多,詳情查看 https://promisesaplus.com/,我們在實現 Promise 邏輯時會根據實現的部分介紹相關的 Promise/A+ 規范內容。
在 Promise/A+ 規范中規定:
構造函數的參數為一個名為 executor 的執行器,即函數,在創建實例時該函數內部邏輯為同步,即立即執行;
executor 執行時的參數分別為 resolve 和 reject,一個為成功時執行的函數,一個為失敗時執行的函數;
在 executor 執行時,一旦出現錯誤立即調用 reject 函數,并設置錯誤信息給 reason 屬性;
每個 Promise 實例有三個狀態 pending、fulfilled 和 rejected,默認狀態為 pending;
狀態只能從 pending 到 fulfilled 或從 pending 到 rejected,且不可逆;
執行 resolve 函數會使狀態從 pending 變化到 fulfilled 并將參數存入實例的 value 屬性中;
執行 reject 函數會使狀態從 pending 變化到 rejected 并將錯誤信息存入實例的 reason 屬性中。
針對上面的 Promise/A+ 規范,Promise 構造函數代碼實現如下:
// promise.js -- Promise 構造函數 function Promise(executor) { var self = this; self.status = "pending"; // 當前 Promise 實例的狀態 self.value = undefined; // 當前 Promise 實例成功狀態下的值 self.reason = undefined; // 當前 Promise 實例失敗狀態的錯誤信息 self.onFulfilledCallbacks = []; // 存儲成功的回調函數的數組 self.onRejectedCallbacks = []; // 存儲失敗的回調函數的數組 // 成功的執行的函數 function resolve(value) { if (self.status === "pending") { self.status = "fulfilled"; self.value = value; // 每次調用 resolve 時,執行 onFulfilledCallbacks 內部存儲的所有的函數(在實現 then 方法中詳細說明) self.onFulfilledCallbacks.forEach(function(fn) { fn(); }); } } // 失敗執行的函數 function reject(reason) { if (self.status === "pending") { self.status = "rejected"; self.reason = reason; // 每次調用 reject 時,執行 onRejectedCallbacks 內部存儲的所有的函數(在實現 then 方法中詳細說明) self.onRejectedCallbacks.forEach(function(fn) { fn(); }); } } // 調用執行器函數 try { executor(resolve, reject); } catch (e) { // 如果執行器執行時出現錯誤,直接調用失敗的函數 reject(e); } } // 將自己的 Promise 導出 module.exports = Promise;
上面構造函數中的 resolve 和 reject 方法在執行的時候都進行了當前狀態的判斷,只有狀態為 pending 時,才能執行判斷內部邏輯,當兩個函數有一個執行后,此時狀態發生變化,再執行另一個函數時就不會通過判斷條件,即不會執行判斷內部的邏輯,從而實現了兩個函數只有一個執行判斷內部邏輯的效果,使用如下:
// verify-promise.js -- 驗證 promise.js 的代碼 // 引入自己的 Promise 模塊 // 因為都驗證代碼都寫在 verify-promise.js 文件中,后面就不再引入了 const Promise = require("./promise.js"); let p = new Promise((resolve, reject) => { // ...同步代碼 resolve(); reject(); // 上面兩個函數只有先執行的 resolve 生效 });
沒有 Promise 之前在一個異步操作的回調函數中返回一個結果在輸入給下一個異步操作,下一個異步操作結束后需要繼續執行回調,就形成回調函數的嵌套,在 Promise 中,原來回調函數中的邏輯只需要調用當前 Promise 實例的 then 方法,并在 then 方法的回調中執行,改變了原本異步的書寫方式。
在 then 方法中涉及到的 Promise/A+ 規范:
Promise 實例的 then 方法中有兩個參數,都為函數,第一個參數為成功的回調 onFulfilled,第二個參數為失敗的回調 onRejected;
當 Promise 內部執行 resolve 時,調用實例的 then 方法執行成功的回調 onFulfilled,當 Promise 內部執行 reject 或執行出錯時,調用實例的 then 方法執行錯誤的回調 onRejected;
then 方法需要支持異步,即如果 resovle 或 reject 執行為異步時,then 方法的回調 onFulfilled 或 onRejected 需要在后面執行;
Promise 需要支持鏈式調用,Promise 實例調用 then 方法后需要返回一個新的 Promise 實例。如果 then 的回調中有返回值且是一個 Promise 實例,則該 Promise 實例執行后成功或失敗的結果傳遞給下一個 Promise 實例的 then 方法 onFulfilled (成功的回調)或 onRejected(失敗的回調)的參數,如果返回值不是 Promise 實例,直接將這個值傳遞給下一個 Promise 實例 then 方法回調的參數,then 的回調如果沒有返回值相當于返回 undefined;
Promise 實例鏈式調用 then 時,當任何一個 then 執行出錯,鏈式調用下一個 then 時會執行錯誤的回調,錯誤的回調沒有返回值相當于返回了 undefined,再次鏈式調用 then 時會執行成功的回調;
Promise 實例的鏈式調用支持參數穿透,即當上一個 then 沒有傳遞回調函數,或參數為 null 時,需要后面調用的 then 的回調函數來接收;
executor 在 Promise 構造函數中執行時使用 try...catch... 捕獲異常,但是內部執行的代碼有可能是異步的,所以需要在 then 方法中使用 try...catch... 再次捕獲;
Promise 實例的 then 方法中的回調為 micro-tasks(微任務),回調內的代碼應晚于同步代碼執行,在瀏覽器內部調用微任務接口,我們這里模擬使用宏任務代替。
針對上面的 Promise/A+ 規范,then 方法代碼實現如下:
// promise.js -- then 方法 Promise.prototype.then = function(onFulfilled, onRejected) { // 實現參數穿透 if(typeof onFulfilled !== "function") { onFulfilled = function (data) { return data; } } if(typeof onRejected !== "function") { onRejected = function (err) { throw err; } } // 返回新的 Promise,規范中規定這個 Promise 實例叫 promise2 var promise2 = new Promise(function (resolve, reject) { if (this.status === "fulfilled") { // 用宏任務替代模擬微任務,目的是使 `then` 的回調晚于同步代碼執行 setTimeout(function () { try { // 捕獲異步的異常 // onFulfilled 執行完返回值的處理,x 為成功回調的返回值 var x = onFulfilled(this.value); // 處理返回值多帶帶封裝一個方法 resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); } if (this.status === "rejected") { setTimeout(function () { try { // onRejected 執行完返回值的處理,x 為失敗回調的返回值 var x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); } // 如果在 Promise 執行 resolve 或 renject 為異步 // 將 then 的執行程序存儲在實例對應的 onFulfilledCallbacks 或 onRejectedCallbacks 中 if (this.status === "pending") { this.onFulfilledCallbacks.push(function() { setTimeout(function () { try { var x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); }); this.onRejectedCallbacks.push(function() { setTimeout(function () { try { var x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }.bind(this), 0); }); } }); return promise2; };
在處理 then 回調的返回值時,其實就是在處理該返回值與 then 方法在執行后返回的新 Promise 實例(即 promise2)之間的關系,因為無論 Promise 的執行器在執行 resolve 還是 reject 是同步或是異步,都需要進行處理,所以我們多帶帶封裝一個函數 resolvePromise 來處理。
resolvePromise 函數有四個參數:
promise2:then 執行后返回的 Promise 實例;
x:then 的回調返回的結果;
resolve:promise2 的 resolve 函數;
reject:promise2 的 reject 函數。
在 resolvePromise 函數中涉及到的 Promise/A+ 規范:
將每個 Promise 實例調用 then 后返回的新 Promise 實例稱為 promise2,將 then 回調返回的值稱為 x;
如果 promise2 和 x 為同一個對象,由于 x 要將執行成功或失敗的結果傳遞 promise2 的 then 方法回調的參數,因為是同一個 Promise 實例,此時既不能成功也不能失敗(自己不能等待自己完成),造成循環引用,這種情況下規定應該拋出一個類型錯誤來回絕;
如果 x 是一個對象或者函數且不是 null,就去取 x 的 then 方法,如果 x 是對象,防止 x 是通過 Object.defineProperty 添加 then 屬性,并添加 get 和 set 監聽,如果在監聽中拋出異常,需要被捕獲到,x.then 是一個函數,就當作 x 是一個 Promise 實例,直接執行x 的 then 方法,執行成功就讓 promise2 成功,執行失敗就讓 promise2 失敗,如果 x.then 不是函數,則說明 x 為普通值,直接調用 promise2 的 resolve 方法將 x 傳入,不滿足條件說明該返回值就是一個普通值,直接執行 promise2 的 resolve 并將 x 作為參數傳入;
如果每次執行 x 的 then 方法,回調中傳入的參數還是一個 Promise 實例,循環往復,需要遞歸 resolvePromise 進行解析;
在遞歸的過程中存在內、外層同時調用了 resolve 和 reject 的情況,應該聲明一個標識變量 called 做判斷來避免這種情況。
針對上面的 Promise/A+ 規范,resolvePromise 函數代碼實現如下:
// promise.js -- resolvePromise 方法 function resolvePromise(promise2, x, resolve, reject) { // 判斷 x 和 promise2 是不是同一個函數 if (promise2 === x) { reject(new TypeError("循環引用")); } // x 是對象或者函數并且不是 null,如果不滿足該條件說明 x 只是一個普通的值 if (x !== null && (typeof x === "object" || typeof x === "function")) { // 標識變量,防止遞歸內外層 resolve 和 reject 同時調用 // 針對 Promise,x 為普通值的時候可以放行 var called; // 為了捕獲 Object.defineProperty 創建的 then 屬性時添加監聽所拋出的異常 try { var then = x.then; if (typeof then === "function") { // then 為一個方法,就當作 x 為一個 promise // 執行 then,第一個參數為 this(即 x),第二個參數為成功的回調,第三個參數為失敗的回調 then.call(x, function (y) { if (called) return; called = true; // 如果 y 是 Promise 就繼續遞歸解析 resolvePromise(promise2, y, resolve, reject); }, function (err) { if (called) return; called = true; reject(err); }); } else { // x 是一個普通對象,直接成功即可 resolve(x); } } catch(e) { if (called) return; called = true; reject(e); } } else { resolve(x); } }
上面我們按照 Promise/A+ 規范實現了 Promise 的 then 方法,接下來針對上面的規范,用一些有針對行的案例來對 then 方法一一進行驗證。
驗證異步調用 resolve 或 reject:
// 文件:verify-promise.js // 驗證 promise.js 異步調用 resolve 或 reject let p = new Promise((resolve, reject) => { setTimeout(() => resolve(), 1000); }); p.then(() => console.log("執行了")); // 執行了
驗證鏈式調用 then 返回 Promise 實例:
// 文件:verify-promise.js // 驗證 promise.js then 回調返回 Promise 實例 let p1 = new Promise((resolve, reject) => resolve()); let p2 = new Promise((resolve, reject) => resolve("hello")); p1.then(() => p2).then(data => console.log(data)); // hello
驗證鏈式調用 then 返回普通值:
// 文件:verify-promise.js // 驗證 promise.js then 回調返回普通值 let p = new Promise((resolve, reject) => resolve()); p.then(() => "hello").then(data => console.log(data)); // hello
驗證鏈式調用 then 中執行出錯鏈式調用 then 執行錯誤的回調后,再次鏈式調用 then:
// 文件:verify-promise.js // 驗證 promise.js 鏈式調用 then 中執行出錯鏈式調用 then 執行錯誤的回調后,再次鏈式調用 then let p = new Promise((resolve, reject) => resolve()); p.then(() => { throw new Error("error"); }).then(() => { console.log("success"); }, err => { console.log(err); }).then(() => { console.log("成功"); }, () => { console.log("失敗"); }); // Error: error at p.then... // 成功
驗證 then 的參數穿透:
// 文件:verify-promise.js // 驗證 then 的參數穿透 let p1 = new Promise((resolve, reject) => resolve("ok")); let p2 = p1.then().then(data => { console.log(data); throw new Error("出錯了"); }); p2.then().then(null, err => console.log(err)); // ok // 出錯了
驗證 then 方法是否晚于同步代碼執行:
// 文件:verify-promise.js // 驗證 then 方法是否晚于同步代碼執行 let p = new Promise((resolve, reject) => { resolve(1); }); p.then(data => console.log(data)); console.log(2); // 2 // 1
驗證循環引用:
// 文件:verify-promise.js // 驗證 promise.js 循環引用 let p1 = new Promise((resolve, reject) => resolve()); // 讓 p1 then 方法的回調返回自己 var p2 = p1.then(() => { return p2; }); p2.then(() => { console.log("成功"); }, err => { console.log(err); }); // TypeError: 循環引用 at resolvePromise...
驗證 then 回調返回對象通過 Object.definePropertype 添加 then 屬性并添加 get 監聽,在觸發監聽時拋出異常:
// 文件:verify-promise.js // 驗證 promise.js then 回調返回對象通過 Object.definePropertype 添加 then 和 get 監聽,捕獲異常 let obj = {}; Object.defineProperty(obj, "then", { get () { throw new Error(); } }); let p = new Promise((resolve, reject) => resolve()); p.then(() => { return obj; }).then(() => { console.log("成功"); }, () => { console.log("出錯了"); }); // 出錯了
驗證每次執行 resolve 都傳入 Promise 實例,需要將最終的執行結果傳遞給下一個 Promise 實例 then 的回調中:
// 文件:verify-promise.js // 驗證 promise.js 每次執行 resolve 都傳入 Promise 實例 let p = new Promise((resolve, reject) => resolve()); p.then(() => { return new Promise((resolve, reject) => { resolve(new Promise(resolve, reject) => { resolve(new Promise(resolve, reject) => { resolve(200); }); }); }); }).then(data => { console.log(data); }); // 2002、catch 方法的實現
// promise.js -- catch 方法 Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }
catch 方法可以理解為是 then 方法的一個簡寫,只是參數中少了成功的回調,所以利用 Promise/A+ 規范中參數穿透的特性,很容易就實現了 catch 方法,catch 方法的真相就是這么的簡單。
驗證 catch 方法:
// 文件:verify-promise.js // 驗證 promise.js 的 catch 方法 let p = new Promise((resolve, reject) => reject("err")); p.then().catch(err => { console.log(err); }).then(() => { console.log("成功了"); }); // err // 成功了
Promise.resolve 方法傳入一個參數,并返回一個新的 Promise 實例,這個參數作為新 Promise 實例 then 方法成功回調的參數,在調用時感覺直接成功了,其實是直接執行了返回 Promise 實例的 resolve。
// promise.js -- Promise.resolve 方法 Promise.resolve = function (val) { return new Promise(function (resolve, reject) { resolve(val); }); }
驗證 Promise.resolve 方法:
// 文件:verify-promise.js // 驗證 promise.js 的 Promise.resolve 方法 Promise.resolve("成功了").then(data => console.log(data)); // 成功了2、Promise.reject 方法的實現
Promise.reject 方法與 Promise.resolve 的實現思路相同,不同的是,直接調用了返回新 Promise 實例的 reject。
// promise.js -- Promise.reject 方法 Promise.reject = function (reason) { return new Promise(function (resolve, reject) { reject(reason); }); }
驗證 Promise.reject 方法:
// 文件:verify-promise.js // 驗證 promise.js 的 Promise.reject 方法 Promise.reject("失敗了").then(err => console.log(err)); // 失敗了3、Promise.all 方法的實現
Promise.all 方法可以實現多個 Promise 實例的并行,返回值為一個新的 Promise 實例,當所有結果都為成功時,返回一個數組,該數組存儲的為每一個 Promise 實例的返回結果,這些 Promise 實例的返回順序先后不確定,但是返回值的數組內存儲的返回結果是按照數組中 Promise 實例最初順序進行排列的,返回的數組作為返回 Promise 實例成功回調的參數,當其中一個失敗,直接返回錯誤信息,并作為返回 Promise 實例失敗回調的參數。
// promise.js -- Promise.all 方法 Promise.all = function (promises) { return new Promise(function (resolve, reject) { // 存儲返回值 var result = []; // 代表存入的個數,因為 Promise 為異步,不知道哪個 Promise 先成功,不能用數組的長度來判斷 var idx = 0; // 用來構建全部成功的返回值 function processData(index, data) { result[index] = data; // 將返回值存入數組 idx++; if (idx === promises.length) { resolve(result); } } for(var i = 0; i < promises.length; i++) { // 因為 Primise 為異步,保證 i 值是順序傳入 (function (i) { promises[i].then(function (data) { processData(i, data); }, reject); })(i); } }); }
驗證 Promise.all 方法:
// 文件:verify-promise.js // 驗證 promise.js 的 Promise.all 方法 let p1 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve(2), 1000)); Promise.all([p1, p2]).then(data => console.log(data)); // [1, 2] let p3 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p4 = new Promise((resolve, reject) => setTimeout(() => reject(2), 1000)); Promise.all([p3, p4]).then(data => { console.log(data); }).catch(err => { console.log(err); }); // 24、Promise.race 方法的實現
Promise.race 方法與 Promise.all 類似,同樣可以實現多個 Promise 實例的并行,同樣返回值為一個新的 Promise 實例,參數同樣為一個存儲多個 Promise 實例的數組,區別是只要有一個 Promise 實例返回結果,無論成功或失敗,則直接返回這個結果,并作為新 Promise 實例 then 方法中成功或失敗的回調函數的參數。
// promise.js -- Promise.race 方法 Promise.race = function (promises) { return new Promise(function (resolve, reject) { for(var i = 0; i < promises.length; i++) { promises[i].then(resolve, reject); } }); }
驗證 Promise.race 方法:
// 文件:verify-promise.js // 驗證 promise.js 的 Promise.race 方法 let p1 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p2 = new Promise((resolve, reject) => setTimeout(() => resolve(2), 1000)); Promise.race([p1, p2]).then(data => console.log(data)); // 2 let p3 = new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)); let p4 = new Promise((resolve, reject) => setTimeout(() => reject(2), 1000)); Promise.all([p3, p4]).then(data => { console.log(data); }).catch(err => { console.log(err); }); // 2
promises-aplus-test 是專門用來驗證 Promise 代碼是否符合 Promise/A+ 規范的包,需要通過 npm 下載。
npm install promises-aplus-test -g
測試方法:
在 promise.js 中寫入測試代碼;
在命令行中輸入命令 promises-aplus-test + fileName。
測試代碼:
// promise.js -- 測試方法 Promise.derfer // Promise 語法糖 // 好處:解決 Promise 嵌套問題 // 壞處:錯誤處理不方便 Promise.derfer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }
輸入命令:
promises-aplus-test promise.js
執行上面命令后,會根據 Promise/A+ 規范一條一條進行極端的驗證,當驗證通過后會在窗口中這一條對應的執行項前打勾,驗證不通過打叉,直到所有的規范都驗證完畢。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98281.html
摘要:簡介指的是兩個關鍵字,是引入的新標準,關鍵字用于聲明函數,關鍵字用來等待異步必須是操作,說白了就是的語法糖。最后希望大家在讀過異步發展流程這個系列之后,對異步已經有了較深的認識,并可以在不同情況下游刃有余的使用這些處理異步的編程手段。 showImg(https://segmentfault.com/img/remote/1460000018998406?w=1024&h=379); ...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:手寫一款符合規范的長篇預警有點長,可以選擇性觀看。初始狀態是,狀態可以有或者不能從轉換為或者從轉換成即只要由狀態轉換為其他狀態后,狀態就不可變更。 手寫一款符合Promise/A+規范的Promise 長篇預警!有點長,可以選擇性觀看。如果對Promise源碼不是很清楚,還是推薦從頭看,相信你認真從頭看到尾,并且去實際操作了,肯定會有收獲的。主要是代碼部分有點多,不過好多都是重復的,不...
摘要:遍歷器原有的表示集合的數據結構,主要有和,在中又加入了和,這樣就有了四種數據集合,還可以組合使用它們,如數組的成員是或,這樣就需要一種統一的接口機制,用來處理所有不同的數據結構。 showImg(https://segmentfault.com/img/remote/1460000018998438?w=900&h=431); 閱讀原文 Generators 簡介 Generato...
摘要:使用及原理分析通過關鍵字創建實例接受一個參數方法返回兩個方法可用通過在方法中通過調用使成功或調用使失敗來控制狀態中可以執行同步代碼也可以執行異步代碼原型對象上有方法供實例調用方法接受兩個參數默認為一個函數默認為一個函數當狀態為時執行用戶傳入 promise使用及原理分析: 通過new關鍵字創建promise實例, 接受一個executor參數, executor方法返回兩個方法 res...
閱讀 428·2019-08-29 12:44
閱讀 3005·2019-08-26 17:49
閱讀 2417·2019-08-26 13:40
閱讀 1181·2019-08-26 13:39
閱讀 3658·2019-08-26 11:59
閱讀 1820·2019-08-26 10:59
閱讀 2458·2019-08-23 18:33
閱讀 2692·2019-08-23 18:30