lua版promise實現 - 結束

yanghui01發表於2024-08-16

相比V1版本這邊做了以下修改:

1) 函式命名儘量與js版保持一致,js中的then在這邊叫Next(因為then是lua的關鍵字)

2) m_DoNextObj這邊變成了一個列表,這樣改動的結果就是:之前物件間會組成單向連結串列;這邊是一個單向的樹。

3) m_DoNextObj.run函式這邊改成了m_OnFulfilled和m_OnRejected函式(為了與js保持一致)

4) SetFinished函式改成了FulFulled和Reject函式

5) V1版本,Promise物件會作為引數傳入run函式,需要手動呼叫SetFinished;這邊全部封裝在Promise內部自動完成;

Promise.lua

---@class Promise
local Promise = {}
Promise.__cname = "Promise"
Promise.__index = Promise

local State = {
    Pending = 0,
    FulFilled = 1,
    Rejected = -1,
}

------------------ 內部函式 ------------------

---@type Promise
local Inner = {}

local function OnFulFilled_Default(x)
    return x
end

local function OnRejected_Default(x)
    error(x)
end

local function IsCallbackResultPromise(result)
    if "table" == type(result) then
        local result = result.isPromise or "Promise" == result.__cname
        return result
    end
    return false
end

---@param prmNode Promise
---@param otherPrm Promise
function Inner.WaitOtherPromise(prmNode, otherPrm)
    if otherPrm:IsPending() then
        --[[ --這樣會多建立一個臨時Promise物件, 所以這邊用下面的方式
        local tempPrm = otherPrm:Next(function(value)
            prmNode:FulFilled(value)
        end, function(reason)
            prmNode:Reject(reason)
        end)
        ]]
        --otherPrm作為子樹的新Root
        prmNode.m_TransitStateFlag = true
        table.insert(otherPrm.m_NextList, prmNode)
    else
        Inner.TransitionState(prmNode, otherPrm.m_State, otherPrm.m_Value)
    end
end

function Inner:NextRun()
    local nextList = self.m_NextList
    local isFulFilled = (self.m_State == State.FulFilled)

    for i, v in ipairs(nextList) do
        local nextNode = nextList[i]
        if true == nextNode.m_TransitStateFlag then
            --callback被呼叫過了
            Inner.TransitionState(nextNode, self.m_State, self.m_Value)
        else
            local isSucc, result, promiseFlag
            if isFulFilled then
                isSucc, result, promiseFlag = pcall(nextNode.m_OnFulFilled, self.m_Value)
            else
                isSucc, result, promiseFlag = pcall(nextNode.m_OnRejected, self.m_Value)
            end
            if isSucc then
                if promiseFlag or IsCallbackResultPromise(result) then
                    Inner.WaitOtherPromise(nextNode, result)
                else
                    nextNode:FulFilled(result)
                end
            else
                print(debug.traceback(result, 1))
            end
        end
    end
    for i=#nextList,1,-1 do
        nextList[i] = nil
    end
end

function Inner:TransitionState(state, value)
    if self.m_State == state or self.m_State ~= State.Pending then
        return
    end

    self.m_State = state
    self.m_Value = value
    Inner.NextRun(self)
end

------------------

function Promise.new()
    local inst = {}
    setmetatable(inst, Promise)
    inst:ctor()
    return inst
end

function Promise:ctor()
    self.m_State = State.Pending
    self.m_Value = nil

    ---@type Promise[]
    self.m_NextList = {}

    self.m_OnFulFilled = OnFulFilled_Default
    self.m_OnRejected = OnRejected_Default
    self.m_TransitStateFlag = nil
end

---@param onFulFilled fun(value):any
---@param onRejected fun(reason):any
---@return Promise
function Promise:Next(onFulFilled, onRejected)
    local prmNode = Promise.new()
    prmNode.m_OnFulFilled = onFulFilled or OnFulFilled_Default
    prmNode.m_OnRejected = onRejected or OnRejected_Default

    table.insert(self.m_NextList, prmNode)
    if self.m_State ~= State.Pending then
        Inner.NextRun(self)
    end

    return prmNode
end

function Promise:FulFilled(result)
    if self.m_State == State.Pending then
        self.m_State = State.FulFilled
        self.m_Value = result

        Inner.NextRun(self)
    end
end

function Promise:Reject(reason)
    if self.m_State == State.Pending then
        self.m_State = State.Rejected
        self.m_Value = reason

        Inner.NextRun(self)
    end
end

---@return boolean, boolean isPending, isFulFilled
function Promise:GetState()
    local isPending = self.m_State == State.Pending
    local isFulFilled = self.m_State == State.FulFilled
    return isPending, isFulFilled
end

---@return boolean
function Promise:IsPending()
    return self.m_State == State.Pending
end

function Promise:GetValue()
    return self.m_Value
end

return Promise

一些使用例子

先載入A,再載入B,再載入C

local textList = {}
local resAPrm = LoadResAsync("ResA")

local prm1 = resAPrm:Next(function(textA)
    print("ResA load finish")
    table.insert(textList, textA)
    --a載入完, 載入b
    local resBPrm = LoadResAsync("ResB")
    return resBPrm
end)

local prm2 = prm1:Next(function(textB)
    --prm2收到的是resBPrm的值
    print("ResB load finish")
    table.insert(textList, textB)
    --b載入完, 載入c
    local resCPrm = LoadResAsync("ResC")
    return resCPrm
end)

local lastPrm = prm2:Next(function(textC)
    --lastPrm收到的是resCPrm的值
    table.insert(textList, textC)
    print(unpack(textList))
end)

promise執行鏈分析:

關於LoadResAsync

---@type MsgLoop
local MsgLoop = require("MsgLoop")
local _MsgLoop = MsgLoop.new(60)

local function LoadResAsync(res)
    local connector = Promise.new()
    --假裝載入資源
    _MsgLoop:PostRun(function(data)
        connector:FulFilled(data)
    end, 2, res)
    return connector
end


--邏輯程式碼寫在StartLoop前
require("test.lua")

_MsgLoop:StartLoop()

MsgLoop參考這邊:訊息迴圈

相關文章