天天看點

lua函數回調技巧

前言

在使用lua 的開發中,有很多異步調用的場景存在,當某個場景中存在多個異步回調操作且該系列操作中每個操作必須依賴上一個操作的完成,這就形成了

回調地獄

,示例代碼:

function f()
    f1(function ()
        f2(function ()
            f3(function ()
                --coding
            end)
        end)
    end)
end
           

優雅回調

可以想象一個不需要層層嵌套的方式,比如參考js的async.js,而是像瀑布一樣,一個個函數依次調用,示例代碼:

waterfall({
    function (cb)
        f(cb)
    end,
    function cb)
        f1(cb)
    end,
    function (cb)
        f2(cb)
    end,
    function (cb)
        f3(cb)
    end
}, function ()
    -- coding
end)
           

要實作以上的效果,需要定義一個内部函數以及一個參數(回調函數)去調用第一個異步函數,當異步函數執行完成以後調用該回調函數,該回調函數内部繼續調用下一個異步函數,當所有異步函數都執行完成以後調用最終的回調完成整個過程,這裡需要定義一個規範,比如新函數第一個參數為error,如果錯誤了則終端整個執行過程,實作代碼:

function waterfall(tasks, cb)
    local index = 1
    local doNext
    local nextTask = function (...)
        local args = {...}
        local count = select('#', ...)
        args[count + 1] = function (...)
            doNext(...)
        end
        tasks[index](
            table.unpack(args)
        )
    end
    doNext = function (err, ...)
        index = index + 1
        if err or index > #tasks then
            return cb(err, ...)
        end

        nextTask(...)
    end

    nextTask()
end
           

協程

回調的方式的确有點醜陋,代碼量也有點多,如果能像普通調用代碼那樣,讓函數一個個執行,還能達到異步回調的效果,示例代碼:

f()
    f1()
    f2()
    f3()
           

然而這個效果在目前的lua中是無法實作的,但是合理利用協程的話還是可以接近這個效果的,實作代碼:

local function await(fn)
    local run = coroutine.running()
    fn(function()
        coroutine.resume(run)
    end)
    coroutine.yield()
end

function f()
    local co = coroutine.create(function()
        await(f1)
        await(f2)
        await(f3)
    end)
    coroutine.resume(co)
end
           

用協程去執行第一個異步函數,同時跳回主程式,因為協程跟主程式在同一線程,是以,在協程裡調用跟在主程式調用是一樣的,當異步調用完成再跳回協程,繼續下一個異步調用,如此循環.

結束語

解決函數回調地獄的方式有很多種,比如現在比較流行的Promise\保持代碼簡短\子產品化等等.雖然協程\封裝會在一定程度上會增加性能的損耗,但是能更直覺的表達代碼的業務邏輯,簡化開發\維護的成本,始終保持代碼簡潔才是最重要的.