0Pricing
Lua Academy · 课时

使用 pcall 进行受保护调用

使用 pcall 安全捕获错误,并解读返回值。

使用 pcall 进行受保护调用 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。

pcall 基础

pcall(f, ...) 会在受保护模式下使用参数 ... 调用函数 f。如果 f 成功,pcall 会返回 true,后面跟着所有返回值。如果 f 发生错误,pcall 会返回 false,后面跟着错误消息。无论哪种情况,程序都会继续执行。

local function risky(x)
  if x < 0 then error("negative input: " .. x) end
  return math.sqrt(x)
end

local ok, result = pcall(risky, 9)
print(ok, result)    -- true  3.0

ok, result = pcall(risky, -1)
print(ok, result)    -- false  ...: negative input: -1

捕获特定错误

在 pcall 返回后检查错误值的类型,以便区别处理不同种类的错误。表错误包含结构化数据;字符串错误包含消息。请始终处理这两种情况。

local function op(errType)
  if errType == "table" then
    error({code=42, msg="structured error"})
  else
    error("plain string error")
  end
end

local ok, err = pcall(op, "table")
if not ok then
  if type(err) == "table" then
    print("Code:", err.code, "Msg:", err.msg)
  else
    print("String error:", err)
  end
end

成功时的多个返回值

成功时,pcall 会在开头的 true 之后返回受保护函数的所有返回值。请使用多个变量接收这些值。

local function compute(a, b)
  return a + b, a * b, a - b
end

local ok, sum, product, diff = pcall(compute, 6, 4)
if ok then
  print("sum:", sum, "product:", product, "diff:", diff)
  -- sum: 10  product: 24  diff: 2
end

嵌套使用 pcall

pcall 调用可以嵌套。每个 pcall 都会创建独立的受保护作用域。内部 pcall 中的错误由内部 pcall 捕获,而不是由外部 pcall 捕获。这样您就可以实现备用策略。

local function tryPrimary()
  error("primary failed")
end

local function tryFallback()
  return "fallback result"
end

local ok, result = pcall(tryPrimary)
if not ok then
  print("Primary failed:", result)
  ok, result = pcall(tryFallback)
end

if ok then print("Got:", result) end  -- fallback result

对方法使用 pcall

若要通过 pcall 调用方法(冒号语法),请将函数和对象作为前两个参数传入。pcall(obj.method, obj, args...),或者将调用包装在闭包中。

local obj = {
  x = 10,
  compute = function(self, n)
    if n == 0 then error("zero divisor") end
    return self.x / n
  end
}

-- Pass method + self explicitly
local ok, v = pcall(obj.compute, obj, 2)
print(ok, v)   -- true  5.0

-- Or use a closure
ok, v = pcall(function() return obj:compute(0) end)
print(ok, v)   -- false  ...zero divisor

使用 pcall 确保 IO 安全

请将 IO 操作包装在 pcall 中,以捕获意外的 OS 错误。返回一致的结果/错误对,以便调用方能够优雅地处理失败。

local function safeRead(path)
  local ok, result = pcall(function()
    local f = assert(io.open(path, "r"))
    local content = f:read("a")
    f:close()
    return content
  end)
  if ok then return result
  else return nil, result
  end
end

local data, err = safeRead("config.txt")
if data then print("Read", #data, "bytes")
else print("Error:", err)
end

使用 pcall 实现重试模式

对于不稳定的操作(网络请求、文件写入),请使用 pcall 捕获错误,并最多重试 N 次。在实际代码中,请在重试之间暂停(或使用协程)。

local function retry(fn, maxAttempts)
  local attempts = 0
  while attempts < maxAttempts do
    attempts = attempts + 1
    local ok, result = pcall(fn)
    if ok then
      print("Succeeded on attempt", attempts)
      return result
    end
    print("Attempt", attempts, "failed:", result)
  end
  error("all " .. maxAttempts .. " attempts failed")
end

local n = 0
retry(function()
  n = n + 1
  if n < 3 then error("not ready") end
  return "done"
end, 5)

将 pcall 结果收集到表中

在循环中使用 pcall 时,请将结果存入表中,以便稍后分析。这对于批量操作很有用:您可以处理所有项目并收集错误,而不是在第一次失败时停止。

local jobs = {10, -5, 25, 0, 16}
local results = {}

for _, n in ipairs(jobs) do
  local ok, v = pcall(function()
    if n < 0 then error("negative") end
    return math.sqrt(n)
  end)
  results[#results+1] = {input=n, ok=ok, value=v}
end

for _, r in ipairs(results) do
  if r.ok then
    print(r.input, "->", string.format("%.3f", r.value))
  else
    print(r.input, "-> ERROR:", r.value)
  end
end

pcall 与 xpcall

pcall 会捕获错误,但只能为您提供错误值。xpcall 允许您提供一个消息处理函数,该函数会在调用栈仍然完整时运行,从而生成调用栈跟踪。需要调用栈跟踪时,请选择 xpcall。

-- pcall: simple, no traceback
local ok1, err1 = pcall(function()
  error("simple error")
end)
print(err1)   -- only the error message

-- xpcall: with traceback handler
local ok2, err2 = xpcall(
  function() error("traced error") end,
  function(e) return debug.traceback(e, 2) end
)
print(err2)   -- full stack trace

pcall 返回约定

请以 pcall 为基础构建一致的 API:成功时始终返回 value,失败时始终返回 nil, errorMessage。请让函数层层遵循这一方式,以便调用方始终明确预期结果。

local function loadJSON(path)
  local ok, result = pcall(function()
    local f = assert(io.open(path, "r"))
    local text = f:read("a")
    f:close()
    -- Fake JSON parse for demo
    return {data = text, path = path}
  end)
  if ok then return result
  else return nil, result
  end
end

local data, err = loadJSON("data.json")
if data then print("Loaded:", data.path)
else print("Failed:", err)
end

快速检查

受保护函数成功时,pcall 会返回什么?

回顾:pcall

总结:

  • pcall(f, ...) → true, results... 或 false, err
  • 检查第一个返回值,以确定成功还是失败
  • 成功时,true 后面跟着多个返回值
  • 嵌套 pcall:每个 pcall 独立捕获自身的错误
  • 重试模式:使用 pcall 循环处理不稳定的操作
  • 需要调用栈跟踪时使用 xpcall

常见问题解答

「使用 pcall 进行受保护调用」课时是免费的吗?

是的 — 「使用 pcall 进行受保护调用」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「使用 pcall 进行受保护调用」这节课中我会学到什么?

使用 pcall 安全捕获错误,并解读返回值。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 pcall 进行受保护调用」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. error() 函数
  2. 使用 pcall 进行受保护调用
  3. xpcall 和消息处理器
  4. 结构化错误对象
← 返回 Lua Academy