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\保持代码简短\模块化等等.虽然协程\封装会在一定程度上会增加性能的损耗,但是能更直观的表达代码的业务逻辑,简化开发\维护的成本,始终保持代码简洁才是最重要的.