免费爱碰视频在线观看,九九精品国产屋,欧美亚洲尤物久久精品,1024在线观看视频亚洲

      手寫 Promise

      基礎版本

      我們先以觀察者模式作為基石來搭建一個基礎版本,實現(xiàn)的功能如下:

    1. 構造函數(shù)接受一個函數(shù) exector 作為參數(shù),該函數(shù)的第一個參數(shù)是 resolve,作用是把 Promise 對象狀態(tài)變?yōu)椤俺晒Α薄?/li>
    2. 原型方法 then 是用來注冊一個當狀態(tài)變?yōu)槌晒Φ幕卣{(diào)函數(shù),當回調(diào)觸發(fā)時,參數(shù)是 resolve 時的決議值。
    3. function Promise(exector) { this.pending = []; this.value = undefined; const resolve = value => { if (this.pending) { this.value = value; for (const onFulfilled of this.pending) { // 通知觀察者。 onFulfilled(this.value); } this.pending = undefined; } }; exector(resolve);}Promise.prototype.then = function (onFulfilled) { if (this.pending) { // 還沒決議,先注冊觀察者。 this.pending.push(onFulfilled); } else { // 已決議,直接通知。 onFulfilled(this.value); }};// 測試一下。const p = new Promise(resolve => { setTimeout(() => resolve(666), 100);})p.then(res => console.log(‘res: %s’, res));// 輸出:// res: 666

      代碼很簡單,應該不用過多解釋,上面的完整代碼在這里:p0.js[2]。

      這個基礎版本有個明顯的問題:then 不能進行鏈式調(diào)用,接著就來優(yōu)化一下。

      then鏈式調(diào)用

      then 的鏈式調(diào)用會返回一個新的 Promise,并且 then 中回調(diào)的返回值會使這個新的 Promise 決議為“成功”狀態(tài)。

      Promise.prototype.then = function (onFulfilled) { // “當前”Promise,對于返回的新 Promise 而言,也是“前一個”Promise。 const prev = this; const promise = new Promise(resolve => { // 包裝 onFulfilled,使其可以“傳播”決議; // “前一個” Promise 決議后,決議返回的這個新 Promise。 const onSpreadFulfilled = function (value) { resolve(onFulfilled(value)); }; if (prev.pending) { prev.pending.push(onSpreadFulfilled); } else { onSpreadFulfilled(prev.value); } }); return promise;};// 測試一下。const p = new Promise(resolve => { setTimeout(() => resolve(666), 100);});p.then(res => { console.log(‘res1: %s’, res); return res + 1;).then(res => { console.log(‘res2: %s’, res);); // 輸出:// res1: 666// res2: 667

      實現(xiàn)鏈式調(diào)用的關鍵是如何決議返回的新 Promise?這里我對變量做了一些有含義的命名,方便理解:

    4. prev 是調(diào)用 then 時“當前”的 Promise,對于返回的新 Promise 而言,可以看做是“前一個”Promise。
    5. 包裝 onFulfilled——執(zhí)行完當前注冊的 onFulfilled 后,用其返回值來決議返回的那個新的 Promise。這是個關鍵步驟,為體現(xiàn)傳播的動作,將其命名為 onSpreadFulfilled。
    6. 將 onSpreadFulfilled 作為成功的回調(diào)注冊到 prev 上。
    7. 上面的完整代碼在這里:p1.js[3]。

      現(xiàn)在又有個新問題,如果 resolve 的 value 是個 Promise,或者 onfulfilled 函數(shù)返回的結(jié)果是個 Promise,那么鏈式傳播的決議值不應該是這個 Promise 本身,而是這個 Promise 的決議值才對,也就是要支持 Promise 的狀態(tài)傳遞。

      狀態(tài)傳遞

      在實現(xiàn)狀態(tài)傳遞之前,我們先來康康如何確定一個值是不是 Promise。我們可以用原型繼承來判斷:

      return value instanceof Promise;

      這樣的缺點是兼容性較差,你無法強制使用者的運行環(huán)境上下文中只會用一種 Promise 的庫,或者在不同的運行上下文中傳遞 Promise 實例。所以這里我們使用 鴨子類型[4] 來判斷 Promise,重點關注對象的行為,將 Promise 看作是一個 thenable 對象。

      function isPromise(value) { // 如果這個對象上可以調(diào)用 then 方法,就認為它是一個“Promise”了。 return value && typeof value.then === ‘function’;}

      接下來就來實現(xiàn)狀態(tài)傳遞了,實現(xiàn)的思路就是基于鴨子類型和“通知轉(zhuǎn)移”。我們先定義一個函數(shù):

      function wrapToThenable(value) { if (isPromise(value)) { return value; } else { return { then: function (onFulfilled) { return wrapToThenable(onFulfilled(value)); } }; }}

      顧名思義,這個函數(shù)的作用是用來把一個值包裝為 thenable 對象:如果 value 是 Promise 則直接返回;如果不是就包裝并返回一個有 then 方法的對象,也就是 thenable 對象。這個 thenable 對象的作用是啥呢?接著看這里:

      function Promise(exector) { this.pending = []; this.value = undefined; const resolve = value => { if (this.pending) { // 包裝為 thenable。 this.value = wrapToThenable(value); for (const onFulfilled of this.pending) { // 通知時改為調(diào)用 thenable 上的 then。 this.value.then(onFulfilled); } this.pending = undefined; } }; exector(resolve);}

      resolve 決議時,根據(jù) value 的類型不同,有兩種處理情況:

    8. 如果 value 是普通值,經(jīng)過 wrapToThenable 會包裝為 thenable 對象,通知時調(diào)用 then 方法相當于直接調(diào)用 onFulfilled。
    9. 如果 value 是 Promise,則把 onFulfilled 注冊到 value 上;等到 value 決議時,就會調(diào)用 onFulfilled。還記得鏈式調(diào)用時的 onSpreadFulfilled 嗎?這里就是“通知轉(zhuǎn)移”了,把通知下一個 Promise 的責任轉(zhuǎn)移到了 value 身上。
    10. 當然 then 也要做一點修改:

      Promise.prototype.then = function (onFulfilled) { const prev = this; const promise = new Promise(resolve => { const onSpreadFulfilled = function (value) { resolve(onFulfilled(value)); }; if (prev.pending) { prev.pending.push(onSpreadFulfilled); } else { // 這里也要改為調(diào)用 then。 prev.value.then(onSpreadFulfilled); } }); return promise;};// 測試一下。const p = new Promise(resolve => { setTimeout(() => resolve(666), 100);});p.then(res => { console.log(‘res1: %s’, res); return new Promise(resolve => { setTimeout(() => resolve(777), 100); });}).then(res => { console.log(‘res2: %s’, res);});// 輸出:// res1: 666// res2: 777

      這里來總結(jié)一下狀態(tài)傳遞的設計思路。包裝為 thenable 對象非常關鍵,作用是保持了與 Promise 一致的行為,也就是接口一致。這樣在 resolve 時我們不用特定去判斷這個值是不是 Promise,而可以用統(tǒng)一的處理方式來通知觀察者;并且也順便完成了“通知轉(zhuǎn)移”,如果 value 還沒有決議,則 then 會注冊為回調(diào),如果已決議則 then 會立即執(zhí)行。

      上面的完整代碼在這里:p2.js[5]。接下來,我們來完善一下 reject。

      失敗狀態(tài)

      當 Promise 決議失敗時,then 方法里面將只執(zhí)行第二個參數(shù) onRejected 對應的回調(diào)。首先我們需要另一個包裝函數(shù):

      function wrapToRejected(value) { return { then: function (_, onRejected) { return wrapToThenable(onRejected(value)); } };}

      這個函數(shù)的作用是一旦發(fā)生 reject(value) 時,我們把 value 變?yōu)榱硪环N thenable 對象,這個對象在執(zhí)行 then 時只會調(diào)用 onRejected。

      然后改變一下構造函數(shù):

      function Promise(exector) { // pending 變?yōu)橐粋€二維數(shù)組,里面存放的元素是 [onFulfilled, onRejected]。 this.pending = []; this.value = undefined; const resolve = value => { if (this.pending) { this.value = wrapToThenable(value); for (const handlers of this.pending) { this.value.then.apply(this.value, handlers); } this.pending = undefined; } }; const reject = value => { resolve(wrapToRejected(value)); }; exector(resolve, reject);}

      現(xiàn)在有一個比較大的變化:this.pending 變?yōu)榱硕S數(shù)組。這樣 this.value.then.apply 在執(zhí)行時會有三種情況:

    11. this.value 是成功決議轉(zhuǎn)換來的 thenable 對象,還記得 wrapToThenable 嗎?then 被執(zhí)行時只會調(diào)用 onFulfilled。
    12. this.value 是失敗決議轉(zhuǎn)換來的 thenable 對象,then 被執(zhí)行時只會調(diào)用 onRejected。
    13. this.value 是一個 Promise,決議會轉(zhuǎn)移到這個 Promise 上。
    14. 同樣 then 方法也要做一些修改:

      Promise.prototype.then = function (onFulfilled, onRejected) { const prev = this; // 注意這里給了 onFulfilled、onRejected 默認值。 onFulfilled = onFulfilled || function (value) { return value; }; onRejected = onRejected || function (value) { return wrapToRejected(value); }; const promise = new Promise(resolve => { const onSpreadFulfilled = function (value) { resolve(onFulfilled(value)); }; const onSpreadRejected = function (value) { resolve(onRejected(value)); }; if (prev.pending) { prev.pending.push([onSpreadFulfilled, onSpreadRejected]); } else { prev.value.then(onSpreadFulfilled, onSpreadRejected); } }); return promise;};// 測試一下。const p = new Promise((resolve, reject) => { setTimeout(() => reject(666), 100);});p.then(undefined, err => { console.log(‘err1: %s’, err); return 1;}).then(res => { console.log(‘res1: %s’, res);});// 輸出:// err1: 666// res1: 1

      我們要特別注意一下增加了 onFulfilled、onRejected 的默認值。在實際使用 then 時,可能只會專注處理成功或者失敗的回調(diào),但是我們又需要另外一種狀態(tài)要繼續(xù)傳播下去。這里可能有點不好理解,可以代入數(shù)據(jù)模擬一下。上面的完整代碼在這里:p3.js[6]。

      又到了思考總結(jié)時間,thenable 這個接口是關鍵所在。通過兩個包裝對象,分別處理成功和失敗的狀態(tài),在通知觀察者時可以保持統(tǒng)一的邏輯,這個設計是不是感覺很妙呢?

      接下來我們要處理一下調(diào)用時會產(chǎn)生異常的問題。

      異常處理

      我們先思考一下會有哪些地方會產(chǎn)生異常?第一個是構造函數(shù)里面 exector 執(zhí)行的時候:

      function Promise(exector) { this.pending = []; this.value = undefined; const resolve = value => { // … }; const reject = value => { resolve(wrapToRejected(value)); }; try { exector(resolve, reject); } catch (e) { // 如果有異常產(chǎn)生,狀態(tài)變?yōu)椤笆 薄? reject(e); }}

      然后是onFulfilled 和 onRejected 執(zhí)行的時候。當在以上兩個方法里產(chǎn)生異常時,狀態(tài)要變?yōu)槭。⑶倚枰旬惓鞑ハ氯?。then 的改動如下:

      Promise.prototype.then = function (onFulfilled, onRejected) { // … // 產(chǎn)生異常的時候包裝一下。 const errHandler = returnWhenError(err => wrapToRejected(err)); onFulfilled = errHandler(onFulfilled); onRejected = errHandler(onRejected); const promise = new Promise(resolve => { const onSpreadFulfilled = function (value) { resolve(onFulfilled(value)); }; const onSpreadRejected = function (value) { resolve(onRejected(value)); }; if (prev.pending) { prev.pending.push([onSpreadFulfilled, onSpreadRejected]); } else { prev.value.then(onSpreadFulfilled, onSpreadRejected); } }); return promise;};// 封裝為一個可重用的高階函數(shù)。// 如果 fun 執(zhí)行失敗了,則返回 onError 的結(jié)果。function returnWhenError(onError) { return fun => (…args) => { let result; try { result = fun(…args); } catch (e) { result = onError(e); } return result; };}

      然后我們可以加入 catch 方法:

      Promise.prototype.catch = function (onRejected) { // 在 then 中忽略掉“成功”狀態(tài)的回調(diào)。 return Promise.prototype.then.call(this, undefined, onRejected);};// 測試一下。const p = new Promise(resolve => { setTimeout(() => resolve(666), 100);});p.then(res => { console.log(‘res1: %s’, res); throw new Error(‘test error1’);}).then(undefined, err => { console.log(‘err1: %s’, err.message); throw new Error(‘test error2’);}).catch(err => { console.log(‘err2: %s’, err.message);});// 輸出:// res1: 666// err1: test error1// err2: test error2

      上面的完整代碼在這里:p4.js[7]。

      到了這里,基本上 Promise 的基本功能就差不多完成了。不過還有一些不太完善的地方,我們來繼續(xù)做一些優(yōu)化。

      一些優(yōu)化

      封裝私有變量

      this.pending 和 this.value 從外部是可以讀寫的,不夠安全和健壯。而我又還是想用構造函數(shù)和原型方法,不想用閉包來封裝。我這里采用的是 WeakMap[8] 來達到目的,關鍵的修改如下:

      const refMap = new WeakMap();// …function Promise(exector) { // 用當前的實例引用作為 key,把想隱藏的數(shù)據(jù)放進一個對象里。 refMap.set(this, { pending: [], value: undefined }); const resolve = value => { // 取出封裝的數(shù)據(jù)。 const data = refMap.get(this); if (data.pending) { data.value = wrapToThenable(value); for (const handlers of data.pending) { data.value.then.apply(data.value, handlers); } data.pending = undefined; } }; // …}

      同樣 then 也修改一下:

      Promise.prototype.then = function (onFulfilled, onRejected) { // … const promise = new Promise(resolve => { const onSpreadFulfilled = function (value) { resolve(onFulfilled(value)); }; const onSpreadRejected = function (value) { resolve(onRejected(value)); }; // 取出封裝的數(shù)據(jù)。 const data = refMap.get(prev); if (data.pending) { data.pending.push([onSpreadFulfilled, onSpreadRejected]); } else { data.value.then(onSpreadFulfilled, onSpreadRejected); } }); return promise;};

      上面的完整代碼在這里:p5.js[9]。

      當 Promise 實例被垃圾回收時,對應在 WeakMap 中的私有數(shù)據(jù)對象引用也會被消除,沒有內(nèi)存泄漏問題,這種方案非常適合用來封裝私有變量。

      調(diào)用順序

      目前的 Promise 在執(zhí)行時有調(diào)用順序問題,比如:

      const p = new Promise(resolve => resolve(1));p.then(res => { console.log(‘res1:’, res); return res + 1;}).then(res => { console.log(‘res2:’, res);});p.then(res => { console.log(‘res3:’, res);});console.log(‘Hi!’);// 目前的輸出是:// res1: 1// res2: 2// res3: 1// Hi!// 正確的輸出應該是:// Hi!// res1: 1// res3: 1// res2: 2

      一個簡單的做法是利用 setTimeout 來改進:

      function Promise(exector) { // … const resolve = value => { const data = refMap.get(this); if (data.pending) { data.value = wrapToThenable(value); for (const handlers of data.pending) { // 延遲執(zhí)行。 enqueue(() => { data.value.then.apply(data.value, handlers); }); } data.pending = undefined; } }; // …}Promise.prototype.then = function (onFulfilled, onRejected) { // … const promise = new Promise(resolve => { // … if (data.pending) { data.pending.push([onSpreadFulfilled, onSpreadRejected]); } else { // 延遲執(zhí)行。 enqueue(() => { data.value.then(onSpreadFulfilled, onSpreadRejected); }); } }); return promise;};function enqueue(callback) { setTimeout(callback, 1);}

      enqueue 的作用是模擬按入隊順序來延遲執(zhí)行函數(shù)。通過對所有 then 調(diào)用的延遲執(zhí)行,可以保證按正確的注冊順序和決議順序來執(zhí)行了,上面的完整代碼在這里:p6.js[10]。

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權歸原作者所有,如有侵權請聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶投稿
      上一篇 2022年6月17日 09:19
      下一篇 2022年6月17日 13:08

      相關推薦

      • 分享4條發(fā)微商朋友圈的方法(微商朋友圈應該怎么發(fā))

        對于微商朋友來說,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底該怎么發(fā)呢? 為什么同樣是經(jīng)營一個朋友圈,有的微商看起來逼格滿滿,實際效果也不錯;而有的卻動都不動就被屏蔽甚至拉黑…

        2022年11月27日
      • 存儲過程語法(sql server存儲過程語法)

        今天小編給各位分享存儲過程語法的知識,其中也會對sql server存儲過程語法進行解釋,如果能碰巧解決你現(xiàn)在面臨的問題,別忘了關注本站,現(xiàn)在開始吧! oracle存儲過程基本語法…

        2022年11月26日
      • 《寶可夢朱紫》夢特性怎么獲得?隱藏特性獲取方法推薦

        寶可夢朱紫里有很多寶可夢都是擁有夢特性會變強的寶可夢,很多玩家不知道夢特性怎么獲得,下面就給大家?guī)韺毧蓧糁熳想[藏特性獲取方法推薦,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 …

        2022年11月25日
      • 《寶可夢朱紫》奇魯莉安怎么進化?奇魯莉安進化方法分享

        寶可夢朱紫中的奇魯莉安要怎么進化呢?很多玩家都不知道,下面就給大家?guī)韺毧蓧糁熳掀骠斃虬策M化方法分享,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 奇魯莉安進化方法分享 奇魯莉安…

        2022年11月25日
      • 規(guī)范透明促PPP高質(zhì)量發(fā)展——16萬億元大市場迎來新規(guī)

        近日,財政部印發(fā)《關于進一步推動政府和社會資本合作(PPP)規(guī)范發(fā)展、陽光運行的通知》,從做好項目前期論證、推動項目規(guī)范運作、嚴防隱性債務風險、保障項目陽光運行四個方面進一步規(guī)范P…

        2022年11月25日
      • 兩部門推出16條金融舉措促進房地產(chǎn)市場平穩(wěn)健康發(fā)展

        新華社北京11月23日電(記者吳雨)中國人民銀行、銀保監(jiān)會23日公布《關于做好當前金融支持房地產(chǎn)市場平穩(wěn)健康發(fā)展工作的通知》,推出16條金融舉措,促進房地產(chǎn)市場平穩(wěn)健康發(fā)展。 兩部…

        2022年11月24日
      • 《寶可夢朱紫》暴飛龍怎么抓?暴飛龍獲得方法

        寶可夢朱紫暴飛龍位置在哪?在游戲中,很多玩家還不清楚暴飛龍具體要怎么樣獲得,其實獲得方法很簡單,暴飛龍直接是沒得抓的,需要玩家從寶貝龍進化得到,下面一起來看一下寶可夢朱紫暴飛龍獲得…

        2022年11月23日
      • 《寶可夢朱紫》布土撥怎么進化?布土撥進化方法介紹

        寶可夢朱紫中,不同的寶可夢有不同的進化方法,其中布土撥的進化方法是比較特殊的。很多玩家不知道寶可夢朱紫布土撥怎么進化,下面就帶來寶可夢朱紫布土撥進化方法介紹,一起來看看吧,希望能幫…

        2022年11月23日
      • 《寶可夢朱紫》薄荷怎么獲得?薄荷獲得方法

        寶可夢朱紫中薄荷有改變寶可夢的屬性或性格等效果,很多玩家想知道寶可夢朱紫薄荷怎么獲得,下面就帶來寶可夢朱紫薄荷獲得方法,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 薄荷獲得方法…

        2022年11月23日
      • 《寶可夢朱紫》怎么交換精靈?交換精靈方法一覽

        寶可夢朱紫中玩家可以和好友或者npc進行交換寶可夢獲得自己沒有的寶可夢,很多玩家想知道寶可夢朱紫怎么交換精靈,下面就帶來寶可夢朱紫交換精靈方法一覽,感興趣的小伙伴不要錯過,希望能幫…

        2022年11月23日

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時間:周一至周五,10:30-18:30,節(jié)假日休息