lua版promise实现 - 结束

相比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

 

一些使用例子

例1) 先加载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)

执行链分析:

 

例2) A加载完同时加载B, C;B, C全部加载完,再加载D

local prmA = LoadResAsync("ResA")

local prm1 = prmA:Next(function(textA)
    print("ResA load finish")
    local prmBC = Promise.new()
    local loadNumLeft = 2

    local prmB = LoadResAsync("ResB")
    prmB:Next(function(textB)
        print("ResB load finish")
        loadNumLeft = loadNumLeft - 1
        if loadNumLeft == 0 then
            prmBC:FulFilled("")
        end
    end)

    local prmC = LoadResAsync("ResC")
    prmC:Next(function(textC)
        print("ResC load finish")
        loadNumLeft = loadNumLeft - 1
        if loadNumLeft == 0 then
            prmBC:FulFilled("")
        end
    end)

    return prmBC
end)

local prm2 = prm1:Next(function(value)
    print("ResBC load finish")
    local prmD = LoadResAsync("ResD")
    return prmD
end)

local lastPrm = prm2:Next(function(textD)
    print("ResD load finish")
end)

 执行链分析:

 

 

关于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参考这边:消息循环

 

posted @ 2024-08-16 23:28  yanghui01  阅读(6)  评论(0编辑  收藏  举报