personalnadir
personalnadir

Reputation: 652

Avoiding Lua Callback Hell

I work a lot with Lua and Corona SDK, and while I love it as a language, I've realised that my code can get quite messy as callbacks call callbacks and so on.

I was wondering if there were any design patterns or libraries (like async.js for JavsScript) that help reduce the issue.

A typical example would be the use for Corona's transition call:

transition.to(obj,{... onComplete=function()
    transition.to(obj,{... onComplete=function()
        if foo then
            transition.to(obj,{... onComplete=function() ... end})
        else
            transition.to(obj,{... onComplete=function() ... end})
        end
    end})
end})

I find that code quickly become quite dense, but that often inner closures rely on variables from the outer ones. I appreciate that self-discipline is an important factor in creating clean code, but it's useful to have a structure to impose using self-discipline. Other than naming closures has anyone come across a useful way of managing this?

Upvotes: 1

Views: 3009

Answers (2)

Eric
Eric

Reputation: 97631

Using coroutines might help here:

await = function(f)
    return function(...)
        local self = coroutine.running()
        f(..., {onComplete=function(...)
           coroutine.resume(self, ...)
        end})
        return coroutine.yield()
    end
end

await(transition.to)(obj)
await(transition.to)(obj)
if foo then
    await(transition.to)(obj)
else
    await(transition.to)(obj)
end

Or perhaps more generically, addressing the issue in the comments:

async_call = function(f)
    local self = coroutine.running()
    local is_async
    local results = nil
    local async_continue = function(...)
        if coroutine.running() ~= self then
            is_async = true
            coroutine.resume(self, ...)
        else
            is_async = false
            results = {...}
        end
    end
    f(async_continue)
    if is_async then
        return coroutine.yield()
    else
        return unpack(results)
    end
end

async_call(function(cont) transition.to(obj, {onComplete=cont}) end) 

Upvotes: 2

dualed
dualed

Reputation: 10512

One way is to define the callback as a global or upvalue and inject the upvalues the callback would need into it by wrapping the callback in another function:

function foo(upvalue)
    return function(...) -- thats the actual callback
        return print(upvalue, ...);
    end
end

Then you can just attach it as a callback like

transition.to(obj,{... onComplete=foo(somevar)})

The additional function call will however have some small impact on performance. On the other hand, if you have multiple similar callbacks, you could probably come up with some kind of code reuse.

Upvotes: 0

Related Questions