lua原生並沒有提供try-catch的語法來捕獲異常處理,但是提供了pcall/xpcall
等接口,可在保護模式下執行lua函數。
因此,可以通過封裝這兩個接口,來實現try-catch塊的捕獲機制。
我們可以先來看下,封裝後的try-catch使用方式:
try
{
-- try 代碼塊
function ()
error("error message")
end,
-- catch 代碼塊
catch
{
-- 發生異常後,被執行
function (errors)
print(errors)
end
}
}
上面的代碼中,在try塊內部認爲引發了一個異常,並且拋出錯誤消息,在catch中進行了捕獲,並且將錯誤消息進行輸出顯示。
這裏除了對pcall/xpcall
進行了封裝,用來捕獲異常信息,還利用了lua的函數調用語法特性,在只有一個參數傳遞的情況下,lua可以直接傳遞一個table類型,並且省略()
其實try後面的整個{...}
都是一個table而已,作爲參數傳遞給了try函數,其具體實現如下:
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表示運行失敗
並且返回實際的出錯信息,如果想要自定義格式化錯誤信息,可以通過調用xpcall來替換pcall,這個接口與pcall相比,多了個錯誤處理函數:
local ok, errors = xpcall(try, debug.traceback)
你可以直接傳入debug.traceback
,使用默認的traceback處理接口,也可以自定義一個處理函數:
-- 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塊中
也就說,其實上面的實現,完整的支持語法是: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開源工程
原文出處:http://tboox.org/cn/2016/12/14/try-catch/