• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Promise concept inspired by Q library

tarjei

Necronian Engineer
Joined
May 25, 2008
Messages
505
Reaction score
126
Location
Poland
Hello! I was recently inspired by promise concept, and I decided to tackle the implemetation of it in lua. The idea behind it is quite simple. When you perform many callbacks asynchronously , and you have issues in making them execute in particular order, this might be useful to you :)

I used a little tweaked version of class library :

Promise library:
Code:
function createClass(parent)
    local newClass = {}
    newClass.overloads = {}
    function newClass:new(instance)
        local instance = instance or {}
        newClass.overloads.__index = newClass
        setmetatable(instance, newClass.overloads)
        return instance
    end
 
    setmetatable(newClass, {
        __call = function(t, ...)
            local instance = newClass:new()
            if instance.initialize ~= nil then
                instance:initialize(...)
            end
            return instance
        end
    })
 
    if(parent ~= nil) then
        local _mt = getmetatable(newClass)
        _mt.__index = parent
    end

    function newClass:getSelf()
        return newClass
    end

    function newClass:getParent()
        return baseClass
    end

    function newClass:isa(class)
        local tmp = newClass
        while(tmp ~= nil) do
            if(tmp == class) then
                return true
            end

            tmp = tmp:getParent()
        end

        return false
    end
 
    function newClass:setAttributes(attributes)
        for k, v in pairs(attributes) do
            newClass[k] = v
        end
    end

    return newClass
end
function overloadAdd(class, callback)
    class.overloads.__add = callback
end
function overloadSub(class, callback)
    class.overloads.__sub = callback
end
function overloadMul(class, callback)
    class.overloads.__mul = callback
end
function overloadDiv(class, callback)
    class.overloads.__div = callback
end
function overloadPairs(class, callback)
    class.overloads.__pairs = callback
end
function overloadIPairs(class, callback)
    class.overloads.__ipairs = callback
end
function overloadToString(class, callback)
    class.overloads.__tostring = callback
end


Debugger = {}
function Debugger:new(name, enabled)
        local obj = {}
        self.__index = self
        self.__call = function(t, pr)
            return t:err(pr)
        end
        obj.name = name
        obj.enabled = enabled
        setmetatable(obj,self)
        return obj
end
function Debugger:err(t)
    if self.enabled then print("["..tostring(self.name).."] "..tostring(t)) end
end

Promise library:

Code:
Promise = createClass()
Defer = createClass()

STATE_RESOLVED = 1
STATE_REJECTED = 2
STATE_PENDING = 3

function Promise:initialize()
    self.isPromise = true
    self.state = STATE_PENDING
end

function Promise:trigger()
    if self.state == STATE_RESOLVED then
        self.next.parameters = self.promise.data

    elseif state == STATE_REJECTED then
        self.next.parameters = table.pack(self.fcb())
    end
end
function Promise:next(cb)
    self.cb = cb
    self.next = Defer()
    self.next.prev = self


    if self.coroutine ~= nil then
        local done, err = coroutine.resume(self.coroutine)
        if not done then
            print("COROUTINE ERROR")
            print(debug.traceback(self.coroutine, err))
        end
    end

    return self.next.promise
end
function Promise:catch(ecb)
    self.prev.ecb = ecb

end
function Promise:finnaly(fcb)
    self.prev.fcb = fcb
end

function Promise:setState(s)
    self.state = s
end
function Promise:getState(s)
    return self.state
end
function Defer:initialize()
    self.promise = Promise()
end
function Defer:resolve(...)

    local promiseArg = table.pack(...)
    self.promise.coroutine = coroutine.create(function()
        if self.promise:getState() == STATE_PENDING then
            self.promise:setState(STATE_RESOLVED)
            --Resolved before promise has been chained
            while self.promise.cb == null or (self.prev ~= nil and self.prev:getState() == STATE_PENDING) do
                coroutine.yield()
            end
            local passData = self.promise.cb(table.unpack(promiseArg))
            if self.promise.onResult ~= nil then
                self.promise.onResult(passData)
            end
            local function resolveNext(data)
                if self.promise.next then
                    self.promise.next:resolve(data)
                end
            end
            if type(passData)=='table' and passData.isPromise then
                passData:next(function(data) resolveNext(data) end)
            else
                resolveNext(passData)
            end
        end
    end)
    local done, err = coroutine.resume(self.promise.coroutine)
    if not done then
        print("COROUTINE ERROR")
        print(debug.traceback(self.promise.coroutine, err))
    end
end
function Defer:reject(...)
    local promiseArg = table.pack(...)
    self.promise.coroutine = coroutine.create(function()
        if self.promise:getState() == STATE_PENDING then
            self.promise:setState(STATE_REJECTED)
            --Resolved before promise has been chained
            if self.fcb == null then
                coroutine.yield()
            end

            self.promise.cb(table.unpack(arg))

        end
    end)
end

function waitForAll(...)
    local defer = Defer()
    defer:resolve()
    local promise = defer.promise
    local agregateResults = {}
    for p, promiseIter in ipairs(table.pack(...)) do
        promise = promise:next(function(...)

            return promiseIter
        end)
        promise.onResult = (function(result)
            table.insert(agregateResults, result)
        end)
    end

    return promise:next(function()
        return agregateResults
    end)
end
To explain it the easiest way is by example so I brought one with me.


Code:
function doActionOne()
    local deferred = Defer()
    addEvent(function()
        local data = "data1"
       --Now defer is resolved
        deferred.resolve(data)
    end, 3000)
    return deferred.promise
end
function doActionTwo(data)
    local deferred = Defer()
    addEvent(function()
        local data = data .. "data2"
        --Now defer is resolved
        deferred:resolve(data)
    end, 2000)
    return deferred.promise
end

--Agregates promises, and chain a function which executes only if all passed promises will be resolved
waitForAll(doActionOne(), doActionTwo())
:next(function()
     doCreatureSay(cid, "All previous defers were resolved, you can make some action, that means this function will be executed only if both promises from action one and action2 are resolved")
end)
 
Last edited:
Really cool concept, would you mind sharing the resources that helped you learn about it? It's really interesting
 
Very promissing resource, good job!
I did not had fun yet but my initial thought about it:

Improvements:
  • Reject callback
  • Friendly variable names

Well, I did not test it but I got the impression that Finally is not working as intended

I'm going to explore this lib and so give a more appropriate feedback aswell as one or a few new examples.
 
Really cool concept, would you mind sharing the resources that helped you learn about it? It's really interesting
I first encoutered this kind of thing at my job, writing async requests to our backened, to put some pieces of code in certain order. I don't have any materials about promises, although you can google it fast I think.


Very promissing resource, good job!
I did not had fun yet but my initial thought about it:

Improvements:
  • Reject callback
  • Friendly variable names

Well, I did not test it but I got the impression that Finally is not working as intended

I'm going to explore this lib and so give a more appropriate feedback aswell as one or a few new examples.

Yea I know it requires some improvements, although I had just some free time to play with it in lua :) Maybe I will fix that sometime soon :D
 
Seems to be cool, but kind of out dated reading and interpreting code, could you explain a bit it more, what does this could do?
 
Seems to be cool, but kind of out dated reading and interpreting code, could you explain a bit it more, what does this could do?
It's used mainly for async I/O, in this case it's used to make sure that certain events take place after another has already finished.
For example, if you wanted a spell to after finishing casting, cast another wave, and a second wave and a third but one after another. What guarantees do you have that using addEvent they'll be executed in exactly that order? One after another?
You could make it cast each wave with 1s interval, but then it wouldn't be "one wave after another". With promises, you know for sure that the second will execute after the first, the third after the second and so on.

This is used in various others languages and projects, it was kind of interesting to research it after reading about it here.
 
Last edited:
Back
Top