摘要:解析原理,實(shí)現(xiàn)一個(gè)概述這篇文章旨在解析的異步實(shí)現(xiàn)原理,并且以中的為藍(lán)本實(shí)現(xiàn)一個(gè)簡(jiǎn)單的。具體的規(guī)范可以參見細(xì)節(jié)構(gòu)造器中必須傳入函數(shù),否則會(huì)拋出錯(cuò)誤。中的回調(diào)返回值會(huì)影響返回的對(duì)象。執(zhí)行器傳入構(gòu)造器的為函數(shù),并且在構(gòu)造時(shí)就會(huì)執(zhí)行。
解析 Promise 原理,實(shí)現(xiàn)一個(gè)Promise 概述
這篇文章旨在解析 Promise的異步實(shí)現(xiàn)原理,并且以 ES6中的 Promise 為藍(lán)本實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Promise。
通過自己動(dòng)手實(shí)現(xiàn)一個(gè) Promise 對(duì)象,可以熟悉很多可能不知道的 Promise 細(xì)節(jié),同時(shí)也能對(duì)異步的理解更提升一步。
本文假設(shè)讀者對(duì) Promise 規(guī)范有一定理解,并且熟悉 ES6 中的 Promise 基本操作。
Promise 核心Promise 概括來說是對(duì)異步的執(zhí)行結(jié)果的描述對(duì)象。(這句話的理解很重要)
Promise 規(guī)范中規(guī)定了,promise 的狀態(tài)只有3種:
pending
fulfilled
rejected
顧名思義,對(duì)上面3個(gè)狀態(tài)的解釋就不再贅述,Promise 的狀態(tài)一旦改變則不會(huì)再改變。
Promise 規(guī)范中還規(guī)定了 Promise 中必須有 then 方法,這個(gè)方法也是實(shí)現(xiàn)異步的鏈?zhǔn)讲僮鞯幕尽?/p>
具體的規(guī)范可以參見:https://promisesaplus.com
ES6 Promise細(xì)節(jié)Promise 構(gòu)造器中必須傳入函數(shù),否則會(huì)拋出錯(cuò)誤。(沒有執(zhí)行器還怎么做異步操作。。。)
Promise.prototype上的 catch(onrejected) 方法是 then(null,onrejected) 的別名,并且會(huì)處理鏈之前的任何的reject。
Promise.prototype 上的 then和 catch 方法總會(huì)返回一個(gè)全新的 Promise 對(duì)象。
如果傳入構(gòu)造器的函數(shù)中拋出了錯(cuò)誤,該 promise 對(duì)象的[[PromiseStatus]]會(huì)賦值為 rejected,并且[[PromiseValue]]賦值為 Error 對(duì)象。
then 中的回調(diào)如果拋出錯(cuò)誤,返回的 promise 對(duì)象的[[PromiseStatus]]會(huì)賦值為 rejected,并且[[PromiseValue]]賦值為 Error 對(duì)象。
then 中的回調(diào)返回值會(huì)影響 then 返回的 promise 對(duì)象。(下文會(huì)具體分析)
這部分內(nèi)容參考: http://es6.ruanyifeng.com/#do...
動(dòng)手實(shí)現(xiàn)做了上面的鋪墊,實(shí)現(xiàn)一個(gè) Promise 的思路就清晰很多了,本文使用 ES6 來進(jìn)行實(shí)現(xiàn),暫且把這個(gè)類取名為 GPromise吧(不覆蓋原生的,便于和原生進(jìn)行對(duì)比測(cè)試)。下文中 GPromise 代指將要實(shí)現(xiàn)的類,Promise 代指 ES6中的 Promise 類。
內(nèi)部屬性在瀏覽器中打印出一個(gè) Promise 實(shí)例會(huì)發(fā)現(xiàn)其中會(huì)包括兩用"[[ ]]"包裹起來的屬性,這是系統(tǒng)內(nèi)部屬性,只有JS 引擎能夠訪問。
[[PromiseStatus]] [[PromiseValue]]
以上兩個(gè)屬性分別是 Promise 對(duì)象的狀態(tài)和最終值。
我們自己不能實(shí)現(xiàn)內(nèi)部屬性,JS中私有屬性特性(#修飾符現(xiàn)在還是提案)暫時(shí)也沒有支持,所以暫且用"_"前綴規(guī)定私有屬性,這樣就模擬了Promise 中的兩個(gè)內(nèi)部屬性。
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";執(zhí)行器
傳入構(gòu)造器的executor為函數(shù),并且在構(gòu)造時(shí)就會(huì)執(zhí)行。
我們給 executor 中傳入 resolve 和 reject 參數(shù),這兩個(gè)參數(shù)都是函數(shù),用于改變改變 _promiseStatus和 _promiseValue 的值。
并且內(nèi)部做了捕獲異常的操作,一旦傳入的executor 函數(shù)執(zhí)行拋出錯(cuò)誤,GPromise 實(shí)例會(huì)變成 rejected狀態(tài),即 _promiseStatus賦值為"rejected",并且 _promiseValue賦值為Error對(duì)象。
execute(executor) { if (typeof executor != "function") { throw new Error(` GPromise resolver ${executor} is not a function`); } //捕獲錯(cuò)誤 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 對(duì)象在executor 發(fā)生錯(cuò)誤或者reject 時(shí),如果沒有then
或者 catch 來處理,會(huì)把錯(cuò)誤拋出到外部,也就是會(huì)報(bào)錯(cuò)。GPromise 實(shí)現(xiàn)的是沒有向外部拋出錯(cuò)誤,只能由then方法處理。
then 方法內(nèi)部邏輯稍微復(fù)雜點(diǎn),并且有一點(diǎn)一定一定一定要注意到: then 方法中的回調(diào)是異步執(zhí)行的,思考下下段代碼:
console.log(1); new Promise((resolve,reject)=>{ console.log(2); resolve(); }) .then(()=>console.log(3)); console.log(4);
執(zhí)行結(jié)果是什么呢?答案其實(shí)是:1 2 4 3。傳入Promise 中的執(zhí)行函數(shù)是立即執(zhí)行完的啊,為什么不是立即執(zhí)行 then 中的回調(diào)呢?因?yàn)閠hen 中的回調(diào)是異步執(zhí)行,表示該回調(diào)是插入事件隊(duì)列末尾,在當(dāng)前的同步任務(wù)結(jié)束之后,下次事件循環(huán)開始時(shí)執(zhí)行隊(duì)列中的任務(wù)。
then 方法中的難點(diǎn)就是處理異步,其中一個(gè)方案是通過 setInterval來監(jiān)聽GPromise 對(duì)象的狀態(tài)改變,一旦改變則執(zhí)行相應(yīng)then 中相應(yīng)的回調(diào)函數(shù)(onfulfilled和onrejected),這樣回調(diào)函數(shù)就能夠插入事件隊(duì)列末尾,異步執(zhí)行,實(shí)驗(yàn)證明可行,這種方案是最直觀也最容易理解的。
then 方法的返回值是一個(gè)新的 GPromise 對(duì)象,并且這個(gè)對(duì)象的狀態(tài)和 then 中的回調(diào)返回值相關(guān),回調(diào)指代傳入的 onfulfilled 和 rejected。
如果 then 中的回調(diào)拋出了錯(cuò)誤,返回的 GPromise 的 _promiseStatus 賦值為"rejected", _promiseValue賦值為拋出的錯(cuò)誤對(duì)象。
如果回調(diào)返回了一個(gè)非 GPromise 對(duì)象, then返回的 GPromise 的 _promiseStatus 賦值為"resolved", _promiseValue賦值為回調(diào)的返回值。
如果回調(diào)返回了一個(gè) GPromise 對(duì)象,then返回的GPromise對(duì)象 的_promiseStatus和 _promiseValue 和其保持同步。也就是 then 返回的GPromise記錄了回調(diào)返回的狀態(tài)和值,不是直接返回回調(diào)的返回值。
then 方法中的重點(diǎn)邏輯如上,其他參見代碼即可:
then(onfulfilled, onrejected) { let _ref = null, timer = null, result = new GPromise(() => {}); //因?yàn)?promise 的 executor 是異步操作,需要監(jiān)聽 promise 對(duì)象狀態(tài)變化,并且不能阻塞線程 timer = setInterval(() => { if ((typeof onfulfilled == "function" && this._promiseStatus == GPromise.FULFILLED) || (typeof onrejected == "function" && this._promiseStatus == GPromise.REJECTED)) { //狀態(tài)發(fā)生變化,取消監(jiān)聽 clearInterval(timer); //捕獲傳入 then 中的回調(diào)的錯(cuò)誤,交給 then 返回的 promise 處理 try { if (this._promiseStatus == GPromise.FULFILLED) { _ref = onfulfilled(this._promiseValue); } else { _ref = onrejected(this._promiseValue); } //根據(jù)回調(diào)的返回值來決定 then 返回的 GPromise 實(shí)例的狀態(tài) if (_ref instanceof GPromise) { //如果回調(diào)函數(shù)中返回的是 GPromise 實(shí)例,那么需要監(jiān)聽其狀態(tài)變化,返回新實(shí)例的狀態(tài)是根據(jù)其變化相應(yīng)的 timer = setInterval(()=>{ if (_ref._promiseStatus == GPromise.FULFILLED || _ref._promiseStatus == GPromise.REJECTED) { clearInterval(timer); result._promiseValue = _ref._promiseValue; result._promiseStatus = _ref._promiseStatus; } },0); } else { //如果返回的是非 GPromise 實(shí)例 result._promiseValue = _ref; result._promiseStatus = GPromise.FULFILLED; } } catch (e) { //回調(diào)中拋出錯(cuò)誤的情況 result._promiseStatus = GPromise.REJECTED; result._promiseValue = e; } } }, 0); //promise 之所以能夠鏈?zhǔn)讲僮鳎驗(yàn)榉祷亓薌Promise對(duì)象 return result; }測(cè)試用例
是騾子是馬,拉出來溜溜。。
測(cè)試環(huán)境是macOS Sierra 10.12.6,Chrome 60.0.3112.113。
經(jīng)過以下測(cè)試, 證明了GPromise 的基本的異步流程管理和原生 Promise 沒有差別。以下測(cè)試用例參考了 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();
那么再來測(cè)試下鏈?zhǔn)讲僮鳎]有鏈?zhǔn)讲僮鞯?Promise 我要你有何用?),測(cè)試結(jié)果和 Promise 表現(xiàn)一致。
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); } );總結(jié)
到此為止,一個(gè)高仿的 Promise 已經(jīng)實(shí)現(xiàn)完成了,它很簡(jiǎn)單,因?yàn)橹挥幸粋€(gè) then 方法,異步的狀態(tài)管理由內(nèi)部完成。
這里并沒有實(shí)現(xiàn) catch方法,因?yàn)樯衔囊蔡岬搅耍琧atch方法就相當(dāng)于 then(null,onrejected) 。而且 Promise 類上的 race,all,resolve,reject也沒有實(shí)現(xiàn),本文旨在理清 Promise 核心原理,篇幅受限(其實(shí)就是我懶),其他輔助類的方法等之后有時(shí)間再實(shí)現(xiàn)。
本文提供的只是一個(gè)思路,希望能幫助到你,歡迎大家批評(píng)指教。
代碼地址:Github
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/91948.html
摘要:原理分析一說明方法返回一個(gè)被拒絕的對(duì)象。實(shí)現(xiàn)創(chuàng)建一個(gè)新的對(duì)象,通過其構(gòu)造函數(shù)的參數(shù)函數(shù)對(duì)象將狀態(tài)變?yōu)椤:褪褂媒馕鲋担瑫r(shí)通過構(gòu)造函數(shù)的參數(shù)的函數(shù)對(duì)象觸發(fā)的狀態(tài)轉(zhuǎn)變,其中使用數(shù)組記錄返回值使用索引值以確保其返回值在結(jié)果集中的順序。 Promise原理分析二 前面我們分析了Promise的then和catch方法,接下來我們一起來看看reject、resolve、race和all方法的實(shí)現(xiàn)...
摘要:關(guān)于點(diǎn)擊進(jìn)入項(xiàng)目是我于開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。那個(gè)率先改變的實(shí)例的返回值,就傳遞給的回調(diào)函數(shù)。通過插入標(biāo)簽的方式來實(shí)現(xiàn)跨域,參數(shù)只能通過傳入,僅能支持請(qǐng)求。因此清除浮動(dòng),只需要觸發(fā)一個(gè)即可。 關(guān)于【Step-By-Step】 Step-By-Step (點(diǎn)擊進(jìn)入項(xiàng)目) 是我于 2019-05-20 開始的一個(gè)項(xiàng)目,每個(gè)工作日發(fā)布一道面試題。每個(gè)周末我會(huì)仔細(xì)閱讀大家的...
摘要:函數(shù)會(huì)在之后的某個(gè)時(shí)刻觸發(fā)事件定時(shí)器。事件循環(huán)中的這樣一次遍歷被稱為一個(gè)。執(zhí)行完畢并出棧。當(dāng)定時(shí)器過期,宿主環(huán)境會(huì)把回調(diào)函數(shù)添加至事件循環(huán)隊(duì)列中,然后,在未來的某個(gè)取出并執(zhí)行該事件。 原文請(qǐng)查閱這里,略有改動(dòng)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會(huì)通過回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...
閱讀 1877·2023-04-25 19:51
閱讀 1181·2021-11-15 11:43
閱讀 4545·2021-11-02 14:40
閱讀 2009·2021-10-11 10:59
閱讀 1350·2021-09-22 15:05
閱讀 1038·2021-09-09 09:32
閱讀 661·2019-08-30 15:56
閱讀 562·2019-08-30 15:52