Lua 小白入门教程Lua 小白入门教程
首页
基础教程
实战进阶
编程指南
首页
基础教程
实战进阶
编程指南
  • 实战进阶

    • 🚀 实战进阶
    • 第1章 - 模块与包管理
    • 第2章 - 文件 I/O 操作
    • 第3章 - 错误处理
    • 第4章 - 面向对象编程
    • 第5章 - SpringBoot + Lua 整合实战 ⭐
    • 第6章 - Redis + Lua 脚本 ⭐
    • 第7章 - OpenResty 入门
    • 第8章 - 最佳实践与常见坑

第3章 - 错误处理

嗨,朋友!我是长安。

Java 的异常处理你肯定很熟悉:try-catch-finally。Lua 的错误处理机制不太一样,它用的是 pcall 和 xpcall。这一章我们来看看它们怎么用。

🤔 Lua 的错误机制

概念JavaLua
抛出错误throw new Exception()error("消息")
捕获错误try { } catch { }pcall() / xpcall()
断言assert(condition)assert(condition)
错误类型Exception 类体系任意值(通常是字符串)

💥 error() — 抛出错误

-- 抛出一个错误(类似 Java 的 throw)
error("出错了!")

-- 带错误级别
error("出错了!", 1)    -- 级别1:当前函数(默认)
error("出错了!", 2)    -- 级别2:调用者
error("出错了!", 0)    -- 级别0:不添加位置信息

-- error 可以抛出任意类型的值
error({code = 404, message = "Not Found"})
// Java 等价
throw new RuntimeException("出错了!");
throw new NotFoundException("Not Found");

✅ assert() — 断言

-- 如果条件为假,则抛出错误
local function divide(a, b)
    assert(b ~= 0, "除数不能为零!")
    return a / b
end

print(divide(10, 2))    -- 5.0
print(divide(10, 0))    -- 错误:除数不能为零!
// Java 等价
public double divide(double a, double b) {
    if (b == 0) throw new IllegalArgumentException("除数不能为零!");
    return a / b;
}

🛡️ pcall() — 保护调用

pcall(protected call)是 Lua 的 try-catch:

-- pcall 返回:成功标志, 返回值/错误信息
local ok, result = pcall(function()
    return 10 / 2
end)
print(ok, result)    -- true  5.0

-- 捕获错误
local ok, err = pcall(function()
    error("出错了!")
end)
print(ok, err)       -- false  xxx:2: 出错了!

-- 实际应用:安全地调用可能出错的函数
local function riskyOperation()
    -- 可能出错的代码
    local file = assert(io.open("not_exist.txt", "r"))
    return file:read("*a")
end

local ok, result = pcall(riskyOperation)
if ok then
    print("成功: " .. result)
else
    print("失败: " .. result)
end
// Java 等价
try {
    String content = readFile("not_exist.txt");
    System.out.println("成功: " + content);
} catch (Exception e) {
    System.out.println("失败: " + e.getMessage());
}

🔧 pcall 传递参数

local function divide(a, b)
    if b == 0 then
        error("除数不能为零")
    end
    return a / b
end

-- pcall 可以传递参数给被调用函数
local ok, result = pcall(divide, 10, 2)
print(ok, result)    -- true  5.0

local ok, err = pcall(divide, 10, 0)
print(ok, err)       -- false  xxx:3: 除数不能为零

🔍 xpcall() — 带错误处理器的保护调用

xpcall 比 pcall 多一个参数:错误处理函数(类似 Java 的 catch 块中获取堆栈信息)。

-- 错误处理函数(获取堆栈信息)
local function errorHandler(err)
    print("=== 错误详情 ===")
    print("错误信息: " .. tostring(err))
    print("堆栈跟踪:")
    print(debug.traceback("", 2))
    print("================")
    return err
end

local function riskyCode()
    local a = nil
    return a.b    -- 访问 nil 的属性,会出错
end

local ok, err = xpcall(riskyCode, errorHandler)
if not ok then
    print("操作失败,已记录错误日志")
end
// Java 等价
try {
    riskyCode();
} catch (Exception e) {
    System.out.println("=== 错误详情 ===");
    System.out.println("错误信息: " + e.getMessage());
    e.printStackTrace();
    System.out.println("================");
}

📋 pcall vs xpcall

特性pcallxpcall
捕获错误✅✅
错误处理函数❌✅
堆栈信息❌✅
适用场景简单错误处理需要详细错误信息

🔧 实用模式:安全调用封装

-- 封装一个类似 Java try-catch-finally 的函数
local function tryCatch(tryFunc, catchFunc, finallyFunc)
    local ok, result = xpcall(tryFunc, function(err)
        return err
    end)
    
    if not ok then
        if catchFunc then
            catchFunc(result)
        end
    end
    
    if finallyFunc then
        finallyFunc()
    end
    
    return ok, result
end

-- 使用
tryCatch(
    function()    -- try
        print("执行业务逻辑")
        error("模拟错误")
    end,
    function(err) -- catch
        print("捕获错误: " .. tostring(err))
    end,
    function()    -- finally
        print("清理资源")
    end
)
-- 输出:
-- 执行业务逻辑
-- 捕获错误: xxx:3: 模拟错误
-- 清理资源

🔧 实用模式:带重试的函数调用

-- 带重试的安全调用(在网络请求等场景很常用)
local function retryCall(func, maxRetries, delay)
    maxRetries = maxRetries or 3
    delay = delay or 1
    
    for i = 1, maxRetries do
        local ok, result = pcall(func)
        if ok then
            return result
        end
        print(string.format("第 %d 次尝试失败: %s", i, tostring(result)))
        if i < maxRetries then
            -- Lua 标准库没有 sleep,这里用 os.execute 模拟
            os.execute("ping -n 2 127.0.0.1 > nul")  -- Windows
        end
    end
    
    error("重试 " .. maxRetries .. " 次后仍然失败")
end

📝 小结

  • Lua 用 error() 抛出错误,用 pcall() / xpcall() 捕获错误
  • pcall 类似简单的 try-catch,返回 ok, result/error
  • xpcall 支持自定义错误处理函数,可以获取堆栈信息
  • assert() 用于前置条件检查
  • Lua 没有 try-catch-finally 语法,但可以自己封装

➡️ 下一步

错误处理搞定了!下一章来学习 面向对象编程,看看 Lua 怎么用 Table 实现类和继承。

💪 练习题

  1. 写一个函数,使用 pcall 安全地读取文件内容。
  2. 使用 xpcall + debug.traceback 捕获错误并打印堆栈信息。
  3. 封装一个 tryCatchFinally 工具函数。
  4. pcall 和 xpcall 的区别是什么?

答案提示

  1. local ok, content = pcall(function() return io.open("f","r"):read("*a") end)
  2. xpcall(func, function(e) print(debug.traceback(e)) return e end)
  3. 参考本章的 tryCatch 示例
  4. xpcall 多一个错误处理函数参数,可以在错误发生时获取堆栈信息
最近更新: 2026/2/27 17:54
Contributors: 王长安
Prev
第2章 - 文件 I/O 操作
Next
第4章 - 面向对象编程