使用lua实现try-catch异常捕获

lua原生并无提供try-catch的语法来捕获异常处理,可是提供了pcall/xpcall等接口,可在保护模式下执行lua函数。ios

所以,能够经过封装这两个接口,来实现try-catch块的捕获机制。git

咱们能够先来看下,封装后的try-catch使用方式:github

try
{
    -- try 代码块
    function ()
        error("error message")
    end,

    -- catch 代码块
    catch 
    {
        -- 发生异常后,被执行
        function (errors)
            print(errors)
        end
    }
}

上面的代码中,在try块内部认为引起了一个异常,而且抛出错误消息,在catch中进行了捕获,而且将错误消息进行输出显示。api

这里除了对pcall/xpcall进行了封装,用来捕获异常信息,还利用了lua的函数调用语法特性,在只有一个参数传递的状况下,lua能够直接传递一个table类型,而且省略()函数

其实try后面的整个{...} 都是一个table而已,做为参数传递给了try函数,其具体实现以下:ui

function try(block)

    -- get the try function
    local try = block[1]
    assert(try)

    -- get catch and finally functions
    local funcs = block[2]
    if funcs and block[3] then
        table.join2(funcs, block[2])
    end

    -- try to call it
    local ok, errors = pcall(try)
    if not ok then

        -- run the catch function
        if funcs and funcs.catch then
            funcs.catch(errors)
        end
    end

    -- run the finally function
    if funcs and funcs.finally then
        funcs.finally(ok, errors)
    end

    -- ok?
    if ok then
        return errors
    end
end

能够看到这里用了pcall来实际调用try块里面的函数,这样就算函数内部出现异常,也不会中断程序,pcall会返回false表示运行失败lua

而且返回实际的出错信息,若是想要自定义格式化错误信息,能够经过调用xpcall来替换pcall,这个接口与pcall相比,多了个错误处理函数:插件

local ok, errors = xpcall(try, debug.traceback)

你能够直接传入debug.traceback,使用默认的traceback处理接口,也能够自定义一个处理函数:debug

-- traceback
function my_traceback(errors)

    -- make results
    local level = 2    
    while true do    

        -- get debug info
        local info = debug.getinfo(level, "Sln")

        -- end?
        if not info or (info.name and info.name == "xpcall") then
            break
        end

        -- function?
        if info.what == "C" then
            results = results .. string.format("    [C]: in function '%s'\n", info.name)
        elseif info.name then 
            results = results .. string.format("    [%s:%d]: in function '%s'\n", info.short_src, info.currentline, info.name)    
        elseif info.what == "main" then
            results = results .. string.format("    [%s:%d]: in main chunk\n", info.short_src, info.currentline)    
            break
        else
            results = results .. string.format("    [%s:%d]:\n", info.short_src, info.currentline)    
        end

        -- next
        level = level + 1    
    end    

    -- ok?
    return results
end

-- 调用自定义traceback函数
local ok, errors = xpcall(try, my_traceback)

回到try-catch上来,经过上面的实现,会发现里面其实还有个finally的处理,这个的做用是对于try{}代码块,无论是否执行成功,都会执行到finally块中code

也就说,其实上面的实现,完整的支持语法是:try-catch-finally模式,其中catch和finally都是可选的,根据本身的实际需求提供

例如:

try
{
    -- try 代码块
    function ()
        error("error message")
    end,

    -- catch 代码块
    catch 
    {
        -- 发生异常后,被执行
        function (errors)
            print(errors)
        end
    },

    -- finally 代码块
    finally 
    {
        -- 最后都会执行到这里
        function (ok, errors)
            -- 若是try{}中存在异常,ok为true,errors为错误信息,不然为false,errors为try中的返回值
        end
    }
}

或者只有finally块:

try
{
    -- try 代码块
    function ()
        return "info"
    end,

    -- finally 代码块
    finally 
    {
        -- 因为此try代码没发生异常,所以ok为true,errors为返回值: "info"
        function (ok, errors)
        end
    }
}

处理能够在finally中获取try里面的正常返回值,其实在仅有try的状况下,也是能够获取返回值的:

-- 若是没发生异常,result 为返回值:"xxxx",不然为nil
local result = try
{
    function ()
        return "xxxx"
    end
}

能够看到,这个基于pcall的try-catch-finally异常捕获封装,使用上仍是很是灵活的,并且其实现至关的简单

这也充分说明了lua是一门已很是强大灵活,又很是精简的语言。

xmake的自定义脚本、插件开发中,也是彻底基于此异常捕获机制

这样使得扩展脚本的开发很是的精简可读,省去了繁琐的if err ~= nil then返回值判断,在发生错误时,xmake会直接抛出异常进行中断,而后高亮提示详细的错误信息。

例如:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    -- 在编译完ios程序后,对目标程序进行ldid签名
    after_build(function (target))
        os.run("ldid -S %s", target:targetfile())
    end

只须要一行os.run就好了,也不须要返回值判断是否运行成功,由于运行失败后,xmake会自动抛异常,中断程序而且提示错误

若是你想在运行失败后,不直接中断xmake,继续往下运行,能够本身加个try快就好了:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end
        }
    end

若是还想捕获出错信息,能够再加个catch:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end,
            catch 
            {
                function (errors)
                    print(errors)
                end
            }
        }
    end

不过通常状况下,在xmake中写自定义脚本,是不须要手动加try-catch的,直接调用各类api,出错后让xmake默认的处理程序接管,直接中断就好了。。

最后附上try-catch-finally实现的相关完整源码:


我的主页:TBOOX开源工程

相关文章
相关标签/搜索