成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

小而美的Promise庫——promiz源碼淺析

figofuture / 2093人閱讀

摘要:因此,當作為參數(shù)的執(zhí)行任意結(jié)果的回調(diào)函數(shù)時,就會將參數(shù)傳遞給外層的,執(zhí)行對應(yīng)的回調(diào)函數(shù)。

背景

在上一篇博客[[譯]前端基礎(chǔ)知識儲備——Promise/A+規(guī)范](https://segmentfault.com/a/11...,我們介紹了Promise/A+規(guī)范的具體條目。在本文中,我們來選擇了promiz,讓大家來看下一個具體的Promise庫的內(nèi)部代碼是如何運作的。

promiz是一個體積很小的promise庫(官方介紹約為913 bytes (gzip)),作為一個ES2015標準中的Promise的polyfill,實現(xiàn)了諸如resolveallrace等API。

知識儲備

我們在這里簡單回顧一下Promise/A+的主要關(guān)鍵點,如果需要了解詳細內(nèi)容的同學,可以閱讀我的上一篇博客。

Promise有三個狀態(tài),分別為pending、fulfilledrejected,且只能從pendingfulfilled或者rejected,沒有其他的流轉(zhuǎn)方式。

Promise的返回值是一個新的Promise,原因見上一條。

傳遞給then函數(shù)的兩個回調(diào)函數(shù),有且僅有一次機會被執(zhí)行(即執(zhí)行了onfulfilled就不會執(zhí)行onrejected函數(shù),且只執(zhí)行一次)。

代碼實現(xiàn)與分析 異步執(zhí)行器

在介紹Promise之前,我們先介紹一下異步執(zhí)行器。在Promise中,我們需要一個異步的執(zhí)行器來異步執(zhí)行我們的回調(diào)函數(shù)。在規(guī)范中提到,通常情況下,我們可以使用微任務(wù)(nextTick)或者宏任務(wù)(setTimeout)來實現(xiàn)。但是,如果我們需要兼容Web Worker這種情況的話,我們可能還需要一些更多的方式來處理。具體代碼如下:

var queueId = 1
var queue = {}
var isRunningTask = false

// 使用postMessage來執(zhí)行異步函數(shù)
if (!global.setImmediate)
    global.addEventListener("message", function (e) {
        if (e.source == global) {
            if (isRunningTask)
                nextTick(queue[e.data])
            else {
                isRunningTask = true
                try {
                    queue[e.data]()
                } catch (e) {}

                delete queue[e.data]
                isRunningTask = false
            }
        }
    })

/**
 * 異步執(zhí)行方法
 * @param {function} fn 需要執(zhí)行的回調(diào)函數(shù)
 */
function nextTick(fn) {
    if (global.setImmediate) setImmediate(fn)
    // 如果在Web Worker中使用以下方法
    else if (global.importScripts) setTimeout(fn)
    else {
        queueId++
        queue[queueId] = fn
        global.postMessage(queueId, "*")
    }
}

以上代碼比較簡單,我們簡單說明下:

在代碼中,promiz使用了setImmediate、setTimeoutpostMessage這三個方法來執(zhí)行異步函數(shù),其中:

setImmedeate,只有IE實現(xiàn)了該方法,在執(zhí)行完隊列中的代碼后立即執(zhí)行。

PostMessage,新增的H5中的方法。

setTimeout,兼容性最佳,可以適用各種場景。

因此,在promiz的這段代碼中,有一定的兼容性問題,應(yīng)該把setTimeout放到最后作為一個兜底策略,否則無法在老瀏覽器中執(zhí)行。

構(gòu)造函數(shù)

說完了異步函數(shù)執(zhí)行器,我們來看下promise的構(gòu)造函數(shù)。

首先我們來看下內(nèi)存數(shù)據(jù),我們需要存儲當前promise的狀態(tài)、成功的值或者失敗的原因、下一個promise的引用和成功與失敗的回調(diào)函數(shù)。因此,我們需要以下變量:

// states
// 0: pending
// 1: resolving
// 2: rejecting
// 3: resolved
// 4: rejected
var self = this,
    state = 0, // promise狀態(tài)
    val = 0, // success callback返回值
    next = [], // 返回的新的promise對象
    fn, er; // then方法中的成功回調(diào)函數(shù)和失敗回調(diào)函數(shù)

在存儲完相關(guān)數(shù)據(jù)后,我們來看下構(gòu)造函數(shù)。

function Deferred(resolver) {
    ...
    self = this;
    try {
        if (typeof resolver == "function")
            resolver(self["resolve"], self["reject"])
    } catch (e) {
        self["reject"](e)
    }
}

構(gòu)造函數(shù)非常簡單,除了聲明相關(guān)的函數(shù),就只有執(zhí)行傳入的callback而已。當然,如果我們不是鏈式調(diào)用的第一個promise,那么我們會沒有resolver參數(shù),因此不需要在此執(zhí)行,我們會在then函數(shù)執(zhí)行resolve方法。

下面我們來看下上面提到的處理函數(shù)resovlereject。

self["resolve"] = function (v) {
    fn = self.fn
    er = self.er
    if (!state) {
        val = v
        state = 1

        nextTick(fire)
    }
    return self
}

self["reject"] = function (v) {
    fn = self.fn
    er = self.er
    if (!state) {
        val = v
        state = 2

        nextTick(fire)

    }
    return self
}

self["then"] = function (_fn, _er) {
    if (!(this._d == 1))
        throw TypeError()

    var d = new Deferred()

    d.fn = _fn
    d.er = _er
    if (state == 3) {
        d.resolve(val)
    }
    else if (state == 4) {
        d.reject(val)
    }
    else {
        next.push(d)
    }

    return d
}

resolvereject這兩個函數(shù)中,都是改變了內(nèi)部promise的狀態(tài),給定了參數(shù)值,同時異步觸發(fā)了fire函數(shù)。而then方法,則是生成了一個新的Deferred對象,并且完成了相關(guān)的初始化(執(zhí)行完then方法我們就會得到這個新生成的Deferred對象,也就是一個新的Promise);當前一個promise到達resolved狀態(tài)時,不需要等待則直接出發(fā)resolve方法,rejected狀態(tài)時也一樣。那么,讓我們來看下fire方法到底是做什么的呢?

function fire() {

    // 檢測是不是一個thenable對象
    var ref;
    try {
        ref = val && val.then
    } catch (e) {
        val = e
        state = 2
        return fire()
    }

    thennable(ref, function () {
        state = 1
        fire()
    }, function () {
        state = 2
        fire()
    }, function () {
        try {
            if (state == 1 && typeof fn == "function") {
                val = fn(val)
            }

            else if (state == 2 && typeof er == "function") {
                val = er(val)
                state = 1
            }
        } catch (e) {
            val = e
            return finish()
        }

        if (val == self) {
            val = TypeError()
            finish()
        } else thennable(ref, function () {
            finish(3)
        }, finish, function () {
            finish(state == 1 && 3)
        })

    })
}

從上面的代碼來看,fire函數(shù)只是判斷了ref是不是一個thenable對象,然后調(diào)用了thenable函數(shù),傳遞了3個回調(diào)函數(shù)。那么這些回調(diào)函數(shù)到底是做什么用的呢?我們需要來看下thenable函數(shù)的實現(xiàn)代碼。

// ref:指向thenable對象的`then`函數(shù)
// cb, ec, cn : successCallback, failureCallback, notThennableCallback
function thennable(ref, cb, ec, cn) {
    // Promises can be rejected with other promises, which should pass through
    if (state == 2) {
        return cn()
    }
    if ((typeof val == "object" || typeof val == "function") && typeof ref == "function") {
        try {

            // cnt變量用來保證成功和失敗的回調(diào)函數(shù)總共只會被執(zhí)行一次
            var cnt = 0
            ref.call(val, function (v) {
                if (cnt++) return
                val = v
                cb()
            }, function (v) {
                if (cnt++) return
                val = v
                ec()
            })
        } catch (e) {
            val = e
            ec()
        }
    } else {
        cn()
    }
};

在thenable函數(shù)中,如果判斷當前的promise的狀態(tài)是處于rejecting時,會直接執(zhí)行cn,也就是將reject狀態(tài)傳遞下去。而如果當ref不是一個thenable對象的then函數(shù)時(那么此時值為undefined),那么就會直接執(zhí)行cn。

通過fire函數(shù)傳遞的三個callback我們可以看到,cn是在promise的狀態(tài)改變時,針對特定的狀態(tài)來觸發(fā)相對應(yīng)的onfulfilled或者onrejected回調(diào)函數(shù)。

只有當ref是一個thenable時(傳遞給resolve的是一個promise),代碼才會進入上面的try catch邏輯中。

Promise執(zhí)行流程

看完了上面的各部分代碼,我相信大家可能對整個執(zhí)行流程仍然不夠熟悉,下面,我們將這些流程拼接起來,通過幾個完整的流程來說明下。

鏈式調(diào)用第一個Promise

當我們聲明一個promise式,我們會傳入一個resolver。此時,整個Deferred對象的state是0。如果我們在resolver里面調(diào)用了resolve方法,那么我們的state就會變成1,然后出發(fā)fire函數(shù)注冊到thenable函數(shù)里面的第三個回調(diào)函數(shù),從而將值傳遞給下一個thenable。當thenable的then函數(shù)執(zhí)行完成(即我們看到的Promise后面跟著的then函數(shù)執(zhí)行完成以后),我們的state才會變成3,也就是說上一個Promise才會結(jié)束,返回一個新的Promise。

鏈式調(diào)用非第一個Promise

如果不是第一個Promise,那么我們就沒有resolver參數(shù)。因此,我們的resolve方法并不是通過在resolver中進行調(diào)用的,而是將回調(diào)函數(shù)fn注冊進來,在上一個Promise完成后主動調(diào)用執(zhí)行的。也就是說,我們在上一個Promise執(zhí)行完then函數(shù)并且返回一個新的Promise時,我們這個返回的Promise就已經(jīng)進入了resolving的狀態(tài)。

resolve傳遞一個Promise

在Promise/A+規(guī)范中,如果我們給resolve傳遞一個promise,那么我們的通過resolve獲取到的值就是傳遞進去的這個promise返回的值。當然,我們也必須等待作為參數(shù)的這個promise處理完成后,才會處理外面的這個promise。

在promiz的代碼中,我們?nèi)绻ㄟ^resolve接收到一個promise,那么我們在fire函數(shù)中就會吧promise.then的引用傳遞給thenable函數(shù)。在thenable函數(shù)中,我們會將我們當前promise需要執(zhí)行的onfulfilledonrejected封裝成一個函數(shù),傳遞給作為參數(shù)的promise的then函數(shù)。因此,當作為參數(shù)的promise執(zhí)行任意結(jié)果的回調(diào)函數(shù)時,就會將參數(shù)傳遞給外層的promise,執(zhí)行對應(yīng)的回調(diào)函數(shù)。

全局執(zhí)行方法 Promise.all

讓我們先看代碼。

Deferred.all = function (arr) {
    if (!(this._d == 1))
        throw TypeError()

    if (!(arr instanceof Array))
        return Deferred.reject(TypeError())

    var d = new Deferred()

    function done(e, v) {
        if (v)
            return d.resolve(v)

        if (e)
            return d.reject(e)

        var unresolved = arr.reduce(function (cnt, v) {
            if (v && v.then)
                return cnt + 1
            return cnt
        }, 0)

        if (unresolved == 0)
            d.resolve(arr)

        arr.map(function (v, i) {
            if (v && v.then)
                v.then(function (r) {
                    arr[i] = r
                    done()
                    return r
                }, done)
        })
    }

    done()

    return d
}

Promise.all中,我們使用了一個計數(shù)器來進行統(tǒng)計,在每一個Promise后面都增加一個then函數(shù)用于增加計數(shù)。當Promise成功時則計數(shù)+1。當整個數(shù)組中的Promise都已經(jīng)進入resolved狀態(tài)時,我們才會執(zhí)行thenable的then函數(shù)。如果有一個失敗的話,則立即進入reject流程。

總結(jié)

從代碼設(shè)計層面來看,promiz的代碼量較少,閱讀也較為簡單。但是,在某些細節(jié)的設(shè)計上,promiz還是體現(xiàn)出了較為巧妙的思路,如在處理作為入?yún)⒌膒romise時,能夠在這個promise后面動態(tài)的添加一個then函數(shù),從而獲取數(shù)據(jù)給外面的promise。

如果大家有興趣,建議自己根據(jù)本文的說明閱讀一遍源碼,配合Promise/A+規(guī)范來看下是如何實現(xiàn)每一條規(guī)范的。

下一篇博客,我們將為大家從頭開始,來實現(xiàn)一個Promise庫。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/98911.html

相關(guān)文章

  • 小而美的框架—hyperapp

    摘要:寫在最后總體來說,是一個小而美的框架,值得我們來折騰一下,以上均為本人理解,如有錯誤還請指出,不勝感激一個硬廣我所在團隊工作地點在北京求大量前端社招實習,有意者可發(fā)簡歷至 寫在前面 沒錯,又是一個新的前端框架,hyperapp非常的小,僅僅1kb,當然學習起來也是非常的簡單,可以說是1分鐘入門。聲明式:HyperApp 的設(shè)計基于Elm Architecture(這也意味著組件更多的是...

    haitiancoder 評論0 收藏0
  • Riot.js——一個小而美的JS框架

    摘要:專有的內(nèi)容更少,而更多符合標準的成分。當前標簽實例的方法被調(diào)用時當前標簽的任何一個祖先的被調(diào)用時更新從父親到兒子單向傳播。相對來說,微型場景會更適合,不想要太多的外部依賴,又需要組件化數(shù)據(jù)驅(qū)動等更現(xiàn)代化框架的能力。 Riot.js是什么? Riot 擁有創(chuàng)建現(xiàn)代客戶端應(yīng)用的所有必需的成分: 響應(yīng)式 視圖層用來創(chuàng)建用戶界面 用來在各獨立模塊之間進行通信的事件庫 用來管理URL和瀏覽器回...

    nemo 評論0 收藏0
  • Riot.js——一個小而美的JS框架

    摘要:專有的內(nèi)容更少,而更多符合標準的成分。當前標簽實例的方法被調(diào)用時當前標簽的任何一個祖先的被調(diào)用時更新從父親到兒子單向傳播。相對來說,微型場景會更適合,不想要太多的外部依賴,又需要組件化數(shù)據(jù)驅(qū)動等更現(xiàn)代化框架的能力。 Riot.js是什么? Riot 擁有創(chuàng)建現(xiàn)代客戶端應(yīng)用的所有必需的成分: 響應(yīng)式 視圖層用來創(chuàng)建用戶界面 用來在各獨立模塊之間進行通信的事件庫 用來管理URL和瀏覽器回...

    tolerious 評論0 收藏0
  • 小而美的backbone

    摘要:這部分比較容易讓人產(chǎn)生疑惑的是循環(huán)部分,這個循環(huán)有什么用呢舉個例子以上代碼是最簡單的情況,監(jiān)聽事件,當變化時,打印出在源碼中,當?shù)谝淮芜M入后,緊接著被置為,而事件觸發(fā)回調(diào)函數(shù)也不會更改的值,因此再次判斷時條件不成立,內(nèi)的代碼段只會執(zhí)行一次。 本文已同步在我的博客 在這個react和vue如日中天、jquery逐漸被大家拋棄的年代,我還是想要來說一說backbone。 16年6月初,在沒...

    sevi_stuo 評論0 收藏0
  • 不可錯過的javascript迷你

    摘要:主張,小而美被實踐是最好用的,本文將介紹筆者收集的一些非常贊的開源庫。是帶有消息通知的數(shù)據(jù)中心,我稱其為會說話的數(shù)據(jù)。迷你檢查庫,這個幾乎涵蓋了全部的各種檢測。最后向大家推薦依稀,這里收集了太多小而美的庫,自己來淘寶吧。 最近看著下自己的github star,把我嚇壞了,手賤黨,收藏癖的我都收藏了300+個倉庫了,是時候整理一下了。 Unix主張kiss,小而美被實踐是最好用的,本文...

    zhangqh 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<