摘要:解析原理,實現一個概述這篇文章旨在解析的異步實現原理,并且以中的為藍本實現一個簡單的。具體的規范可以參見細節構造器中必須傳入函數,否則會拋出錯誤。中的回調返回值會影響返回的對象。執行器傳入構造器的為函數,并且在構造時就會執行。
解析 Promise 原理,實現一個Promise 概述
這篇文章旨在解析 Promise的異步實現原理,并且以 ES6中的 Promise 為藍本實現一個簡單的 Promise。
通過自己動手實現一個 Promise 對象,可以熟悉很多可能不知道的 Promise 細節,同時也能對異步的理解更提升一步。
本文假設讀者對 Promise 規范有一定理解,并且熟悉 ES6 中的 Promise 基本操作。
Promise 核心Promise 概括來說是對異步的執行結果的描述對象。(這句話的理解很重要)
Promise 規范中規定了,promise 的狀態只有3種:
pending
fulfilled
rejected
顧名思義,對上面3個狀態的解釋就不再贅述,Promise 的狀態一旦改變則不會再改變。
Promise 規范中還規定了 Promise 中必須有 then 方法,這個方法也是實現異步的鏈式操作的基本。
具體的規范可以參見:https://promisesaplus.com
ES6 Promise細節Promise 構造器中必須傳入函數,否則會拋出錯誤。(沒有執行器還怎么做異步操作。。。)
Promise.prototype上的 catch(onrejected) 方法是 then(null,onrejected) 的別名,并且會處理鏈之前的任何的reject。
Promise.prototype 上的 then和 catch 方法總會返回一個全新的 Promise 對象。
如果傳入構造器的函數中拋出了錯誤,該 promise 對象的[[PromiseStatus]]會賦值為 rejected,并且[[PromiseValue]]賦值為 Error 對象。
then 中的回調如果拋出錯誤,返回的 promise 對象的[[PromiseStatus]]會賦值為 rejected,并且[[PromiseValue]]賦值為 Error 對象。
then 中的回調返回值會影響 then 返回的 promise 對象。(下文會具體分析)
這部分內容參考: http://es6.ruanyifeng.com/#do...
動手實現做了上面的鋪墊,實現一個 Promise 的思路就清晰很多了,本文使用 ES6 來進行實現,暫且把這個類取名為 GPromise吧(不覆蓋原生的,便于和原生進行對比測試)。下文中 GPromise 代指將要實現的類,Promise 代指 ES6中的 Promise 類。
內部屬性在瀏覽器中打印出一個 Promise 實例會發現其中會包括兩用"[[ ]]"包裹起來的屬性,這是系統內部屬性,只有JS 引擎能夠訪問。
[[PromiseStatus]] [[PromiseValue]]
以上兩個屬性分別是 Promise 對象的狀態和最終值。
我們自己不能實現內部屬性,JS中私有屬性特性(#修飾符現在還是提案)暫時也沒有支持,所以暫且用"_"前綴規定私有屬性,這樣就模擬了Promise 中的兩個內部屬性。
class GPromise { constructor(executor) { this._promiseStatus = GPromise.PENDING; this._promiseValue; this.execute(executor); } execute(executor){ //... } then(onfulfilled, onrejected){ //... } } GPromise.PENDING = "pedding"; GPromise.FULFILLED = "resolved"; GPromise.REJECTED = "rejected";執行器
傳入構造器的executor為函數,并且在構造時就會執行。
我們給 executor 中傳入 resolve 和 reject 參數,這兩個參數都是函數,用于改變改變 _promiseStatus和 _promiseValue 的值。
并且內部做了捕獲異常的操作,一旦傳入的executor 函數執行拋出錯誤,GPromise 實例會變成 rejected狀態,即 _promiseStatus賦值為"rejected",并且 _promiseValue賦值為Error對象。
execute(executor) { if (typeof executor != "function") { throw new Error(` GPromise resolver ${executor} is not a function`); } //捕獲錯誤 try { executor(data => { this.promiseStatus = GPromise.FULFILLED; this.promiseValue = data; }, data => { this.promiseStatus = GPromise.REJECTED; this.promiseValue = data; }); } catch (e) { this.promiseStatus = GPromise.REJECTED; this.promiseValue = e; } }
then方法注:Promise 對象在executor 發生錯誤或者reject 時,如果沒有then
或者 catch 來處理,會把錯誤拋出到外部,也就是會報錯。GPromise 實現的是沒有向外部拋出錯誤,只能由then方法處理。
then 方法內部邏輯稍微復雜點,并且有一點一定一定一定要注意到: then 方法中的回調是異步執行的,思考下下段代碼:
console.log(1); new Promise((resolve,reject)=>{ console.log(2); resolve(); }) .then(()=>console.log(3)); console.log(4);
執行結果是什么呢?答案其實是:1 2 4 3。傳入Promise 中的執行函數是立即執行完的啊,為什么不是立即執行 then 中的回調呢?因為then 中的回調是異步執行,表示該回調是插入事件隊列末尾,在當前的同步任務結束之后,下次事件循環開始時執行隊列中的任務。
then 方法中的難點就是處理異步,其中一個方案是通過 setInterval來監聽GPromise 對象的狀態改變,一旦改變則執行相應then 中相應的回調函數(onfulfilled和onrejected),這樣回調函數就能夠插入事件隊列末尾,異步執行,實驗證明可行,這種方案是最直觀也最容易理解的。
then 方法的返回值是一個新的 GPromise 對象,并且這個對象的狀態和 then 中的回調返回值相關,回調指代傳入的 onfulfilled 和 rejected。
如果 then 中的回調拋出了錯誤,返回的 GPromise 的 _promiseStatus 賦值為"rejected", _promiseValue賦值為拋出的錯誤對象。
如果回調返回了一個非 GPromise 對象, then返回的 GPromise 的 _promiseStatus 賦值為"resolved", _promiseValue賦值為回調的返回值。
如果回調返回了一個 GPromise 對象,then返回的GPromise對象 的_promiseStatus和 _promiseValue 和其保持同步。也就是 then 返回的GPromise記錄了回調返回的狀態和值,不是直接返回回調的返回值。
then 方法中的重點邏輯如上,其他參見代碼即可:
then(onfulfilled, onrejected) { let _ref = null, timer = null, result = new GPromise(() => {}); //因為 promise 的 executor 是異步操作,需要監聽 promise 對象狀態變化,并且不能阻塞線程 timer = setInterval(() => { if ((typeof onfulfilled == "function" && this._promiseStatus == GPromise.FULFILLED) || (typeof onrejected == "function" && this._promiseStatus == GPromise.REJECTED)) { //狀態發生變化,取消監聽 clearInterval(timer); //捕獲傳入 then 中的回調的錯誤,交給 then 返回的 promise 處理 try { if (this._promiseStatus == GPromise.FULFILLED) { _ref = onfulfilled(this._promiseValue); } else { _ref = onrejected(this._promiseValue); } //根據回調的返回值來決定 then 返回的 GPromise 實例的狀態 if (_ref instanceof GPromise) { //如果回調函數中返回的是 GPromise 實例,那么需要監聽其狀態變化,返回新實例的狀態是根據其變化相應的 timer = setInterval(()=>{ if (_ref._promiseStatus == GPromise.FULFILLED || _ref._promiseStatus == GPromise.REJECTED) { clearInterval(timer); result._promiseValue = _ref._promiseValue; result._promiseStatus = _ref._promiseStatus; } },0); } else { //如果返回的是非 GPromise 實例 result._promiseValue = _ref; result._promiseStatus = GPromise.FULFILLED; } } catch (e) { //回調中拋出錯誤的情況 result._promiseStatus = GPromise.REJECTED; result._promiseValue = e; } } }, 0); //promise 之所以能夠鏈式操作,因為返回了GPromise對象 return result; }測試用例
是騾子是馬,拉出來溜溜。。
測試環境是macOS Sierra 10.12.6,Chrome 60.0.3112.113。
經過以下測試, 證明了GPromise 的基本的異步流程管理和原生 Promise 沒有差別。以下測試用例參考了 MDN 中的[Promise
API](https://developer.mozilla.org... 中的 Advanced Example。
var promiseCount = 0; function test(isPromise) { let thisPromiseCount = ++promiseCount, executor = (resolve, reject) => { console.log(thisPromiseCount + ") Promise started (Async code started)"); window.setTimeout( function () { resolve(thisPromiseCount); }, Math.random() * 2000 + 1000); }; console.log(thisPromiseCount + ") Started (Sync code started)"); let p1 = isPromise ? new Promise(executor) : new GPromise(executor); p1.then( function (val) { console.log(val + ") Promise fulfilled (Async code terminated)"); }, function (reason) { console.log("Handle rejected promise (" + reason + ") here."); }); console.log(thisPromiseCount + ") Promise made (Sync code terminated)"); } test(); test(true); test();
那么再來測試下鏈式操作(沒有鏈式操作的 Promise 我要你有何用?),測試結果和 Promise 表現一致。
function async1() { return new GPromise( (resolve, reject) => { console.log("async1 start"); setTimeout(() => { resolve("async1 finished") }, 1000); } ); } function async2() { return new GPromise( (resolve, reject) => { console.log("async2 start"); setTimeout(() => { resolve("async2 finished") }, 1000); } ); } function async3() { return new GPromise( (resolve, reject) => { console.log("async3 start"); setTimeout(() => { resolve("async3 finished"); }, 1000); } ); } async1() .then( data => { console.log(data); return async2(); }) .then( data => { console.log(data); return async3(); } ) .then( data => { console.log(data); } );總結
到此為止,一個高仿的 Promise 已經實現完成了,它很簡單,因為只有一個 then 方法,異步的狀態管理由內部完成。
這里并沒有實現 catch方法,因為上文也提到了,catch方法就相當于 then(null,onrejected) 。而且 Promise 類上的 race,all,resolve,reject也沒有實現,本文旨在理清 Promise 核心原理,篇幅受限(其實就是我懶),其他輔助類的方法等之后有時間再實現。
本文提供的只是一個思路,希望能幫助到你,歡迎大家批評指教。
代碼地址:Github
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91948.html
摘要:原理分析一說明方法返回一個被拒絕的對象。實現創建一個新的對象,通過其構造函數的參數函數對象將狀態變為。和使用解析值,同時通過構造函數的參數的函數對象觸發的狀態轉變,其中使用數組記錄返回值使用索引值以確保其返回值在結果集中的順序。 Promise原理分析二 前面我們分析了Promise的then和catch方法,接下來我們一起來看看reject、resolve、race和all方法的實現...
摘要:關于點擊進入項目是我于開始的一個項目,每個工作日發布一道面試題。那個率先改變的實例的返回值,就傳遞給的回調函數。通過插入標簽的方式來實現跨域,參數只能通過傳入,僅能支持請求。因此清除浮動,只需要觸發一個即可。 關于【Step-By-Step】 Step-By-Step (點擊進入項目) 是我于 2019-05-20 開始的一個項目,每個工作日發布一道面試題。每個周末我會仔細閱讀大家的...
摘要:函數會在之后的某個時刻觸發事件定時器。事件循環中的這樣一次遍歷被稱為一個。執行完畢并出棧。當定時器過期,宿主環境會把回調函數添加至事件循環隊列中,然后,在未來的某個取出并執行該事件。 原文請查閱這里,略有改動。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第四章。 現在,我們將會通過回顧單線程環境下編程的弊端及如何克服這些困難以創建令人驚嘆...
閱讀 1867·2023-04-25 19:51
閱讀 1167·2021-11-15 11:43
閱讀 4529·2021-11-02 14:40
閱讀 1999·2021-10-11 10:59
閱讀 1338·2021-09-22 15:05
閱讀 1027·2021-09-09 09:32
閱讀 648·2019-08-30 15:56
閱讀 549·2019-08-30 15:52