Skip to content

mt-mods/promise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

1f5485a · Mar 19, 2025

History

77 Commits
Jul 7, 2024
Apr 11, 2023
Jul 7, 2024
Feb 18, 2025
Feb 18, 2025
Feb 26, 2025
Feb 25, 2025
Feb 25, 2025
Feb 18, 2025
Feb 17, 2025
Feb 17, 2025
Feb 28, 2025
Feb 28, 2025
Mar 19, 2025
Apr 11, 2023
Apr 11, 2023
Mar 19, 2025
Mar 19, 2025
Feb 25, 2025
Feb 25, 2025
Mar 19, 2025
Apr 11, 2023
Feb 3, 2025
Mar 13, 2025
Mar 13, 2025
Apr 12, 2023

Repository files navigation

promise library for minetest

License Download Coverage Status

Overview

Features:

  • Async event handling
  • Utilities for formspec, emerge_area, handle_async, http and minetest.after
  • async/await helpers (js example here)

Examples

Simple promise and handling:

-- create promise
local p = Promise.new(function(resolve, reject)
    -- async operation here, mocked for this example
    minetest.after(1, function()
        resolve("result-from-a-long-operation")
    end)
end)

-- handle the result later
p:next(function(result)
    assert(result == "result-from-a-long-operation")
end)

Chained async operations:

Promise.emerge_area(pos1, pos2):next(function()
    -- delay a second before next operation
    return Promise.after(1)
end):next(function()
    -- called after emerge + 1 second delay
end)

Wait for multiple http requests:

local http = minetest.request_http_api()
local toJson = function(res) return res.json() end

local p1 = Promise.http(http, "http://localhost/x"):next(toJson)
local p2 = Promise.http(http, "http://localhost/y"):next(toJson)

Promise.all(p1, p2):next(function(values)
    local x = values[1]
    local y = values[2]
end)

Wait for multiple async workers:

local fn = function(x,y)
    return x*y
end

local p1 = Promise.handle_async(fn, 1, 1)
local p2 = Promise.handle_async(fn, 2, 2)
local p3 = Promise.handle_async(fn, 10, 2)

Promise.all(p1, p2, p3):next(function(values)
    assert(values[1] == 1)
    assert(values[2] == 4)
    assert(values[3] == 20)
end)

Api

Promise.new(callback)

Creates a new promise

Example:

local p = Promise.new(function(resolve, reject)
    -- TODO: async operation and resolve(value) or reject(err)
end)

-- test if the value is a promise
assert(p.is_promise == true) -- field value
assert(Promise.is_promise(p)) -- function

p:then(function(result)
    -- TODO: handle the result
end):catch(function(err)
    -- TODO: handle the error
end)

p:finally(function()
    -- always called after error or success
    -- TODO: handle cleanup/common things here
end)

Alternatively:

-- promise without callback
local p = Promise.new()
-- later on: resolve from outside
p:resolve(result)

NOTE: pass a 0 to the error function if you want to evaluate the error directly:

Promise.new(function()
    error("nope", 0)
end):catch(function(err)
    assert(err == "nope")
end)

Reference: https://www.lua.org/manual/5.3/manual.html#pdf-error

Promise.resolve(value)

Returns an already resolved promise with given value

Promise.reject(err)

Returns an already rejected promise with given error

Promise.empty()

Returns an already resolved promise with a nil value

Promise.all(...)

Wait for all promises to finish

Example:

local p1 = Promise.resolve(5)
local p2 = Promise.resolve(10)

Promise.all(p1, p2):next(function(values)
    assert(#values == 2)
    assert(values[1] == 5)
    assert(values[2] == 10)
end)

Promise.race(...)

Wait for the first promise to finish

Example:

local p1 = Promise.resolve(5)
local p2 = Promise.new()

Promise.race(p1, p2):next(function(v)
    assert(v == 5)
end)

The race() function can be used for timeouts, for example:

local p = Promise.new() -- never resolves
local to = Promise.timeout(5) -- rejects after 5 seconds

Promise.race(p, to):next(function(v)
    -- process "v"
end):catch(function(err)
    -- timeout reached (err == "timeout")
end)

Promise.any(...)

Returns the first fulfilled promise or rejects if all promises reject.

Promise.after(delay, value?, err?)

Returns a delayed promise that resolves to given value or error

Promise.timeout(delay)

Returns a promise that rejects with "timeout" after the given delay. Useful in comination with Promise.race()

Promise.emerge_area(pos1, pos2?)

Emerges the given area and resolves afterwards

Promise.formspec(playername, formspec, callback?)

Formspec shorthand / util

Example:

Promise.formspec(playername, "size[2,2]button_exit[0,0;2,2;mybutton;label]")
:next(function(fields)
    -- formspec closed
    assert(fields.mybutton == true)
end)

NOTE: the promise only resolves if the player exits the formspec (with a quit="true" value, a default in exit_buttons)

Example with optional scroll/dropdown callbacks:

local callback = function(fields)
    -- TODO: handle CHG, and other "non-quit" events here
end

Promise.formspec(playername, "size[2,2]button_exit[0,0;2,2;mybutton;label]", callback)
:next(function(fields)
    -- formspec closed
    assert(fields.mybutton == true)
end)

Promise.handle_async(fn, args...)

Executes the function fn in the async environment with given arguments

NOTE: This falls back to a simple function-call if the minetest.handle_async function isn't available.

Promise.http(http, url, opts?)

Http query

  • http The http instance returned from minetest.request_http_api()
  • url The url to call
  • opts Table with options:
    • method The http method (default: "GET")
    • timeout Timeout in seconds (default: 10)
    • data Data to transfer, serialized as json if type is table
    • headers table of additional headers

Rejects with Promise.HTTP_TIMEOUT in case of timeouts (or connection errors)

Examples:

local http = minetest.request_http_api()

-- call chuck norris api: https://api.chucknorris.io/ and expect json-response
Promise.http(http, "https://api.chucknorris.io/jokes/random"):next(function(res)
    return res.json()
end):next(function(joke)
    assert(type(joke.value) == "string")
end):catch(function(e)
    -- conection refused or timed out:
    -- e == Promise.HTTP_TIMEOUT
end)

-- post json-payload with 10 second timeout and expect raw string-response (or error)
Promise.http(http, "http://localhost/stuff", { method = "POST", timeout = 10, data = { x=123 } }):next(function(res)
    return res.text()
end):next(function(result)
    assert(result)
end):catch(function(res)
    -- something went wrong with the http call itself (no response)
    -- dump the raw http response (res.code, res.timeout)
    print(dump(res))
end)

Promise.json(http, url, opts?)

Helper function for Promise.http that parses a json response

HTTP Status code handling:

  • 200: resolves with a parsed json object
  • 204 or 404: resolves with a nil value
  • Everything else: rejects with unexpected status-code

Example:

-- call chuck norris api: https://api.chucknorris.io/ and expect json-response
Promise.json(http, "https://api.chucknorris.io/jokes/random"):next(function(joke)
    assert(type(joke.value) == "string")
end, function(err)
    -- request not successful or response-status not 200
    print("something went wrong while calling the api: " .. err)
end)

Promise.mods_loaded()

Resolved on mods loaded (minetest.register_on_mods_loaded)

Example:

Promise.mods_loaded():next(function()
    -- stuff that runs when all mods are loaded
end)

Promise.joinplayer(playername, timeout?)

Resolves with the player object when the player joins (defaults to a 5 second timeout)

Promise.leaveplayer(playername, timeout?)

Resolves when the player leaves (does not resolve with a value, unlike joinplayer)

Promise.asyncify(fn)

Turns a normal function into an async function. The first parameter will be the await function.

Example:

-- normal function
local fn = function(await,a,b,c)
    assert(type(await) == "function")
    assert(a == 1)
    assert(b == 2)
    assert(c == 3)
    await(Promise.after(0))
    return "ok"
end

-- async function
local async_fn = Promise.asyncify(fn)

-- invoke with params
local p = async_fn(1,2,3)

-- use as a promise
p:next(function(v)
    assert(v == "ok")
end)

Promise.on_punch_pos(pos, timeout?)

Resolves when the node at pos is hit or throws an error if the timeout (in seconds, default: 5) is reached. Resolving value:

{
    pos = Vector,
    node = {name="", ...},
    puncher = PlayerObj,
    pointed_thing = { ... }
}

Promise.on_punch_nodename(nodename, timeout?)

Resolves when the node with name nodename is hit or throws an error if the timeout (in seconds, default: 5) is reached. Resolving value is the same as Promise.on_punch_pos

Promise.on_punch_playername(playername, timeout?)

Resolves when a node is hit by the player with name playername or throws an error if the timeout (in seconds, default: 5) is reached. Resolving value is the same as Promise.on_punch_pos

Promise.dynamic_add_media(options)

Dynamic media push

Example:

Promise.dynamic_add_media({ filepath = "world/image.png", to_player = "singleplayer" })
:next(function(name)
    -- player callback
end):catch(function()
    -- error handling
end)

NOTE: experimental, only works if the to_player property is set

Promise.register_chatcommand(cmd, def)

Chatcommand helper with wrappers for success and error. Shows messages after the returned promise fails or succeeds and prints the error or success-value if the type is "string"

Usage:

Promise.register_chatcommand("something", {
    description = "Does something",
    func = function(name)
        return Promise.new(function(resolve, reject)
            resolve("processed 123 items")
            -- or: reject("something went wrong")
        end)
    end,
    handle_success = false, -- optional, if false: disable success message
    handle_error = false -- optional, if false: disable error message
})

async/await with Promise.async

Similar to javascripts implementation async/await can be used in lua too with the help of coroutines

Example: fetch a joke with async/await

Promise.async(function(await)
    local joke = await(Promise.json(http, "https://api.chucknorris.io/jokes/random"))
    assert(type(joke.value) == "string")
    -- do stuff here with the joke
end)

Example: sleep for a few seconds

Promise.async(function(await)
    await(Promise.after(5))
    -- 5 seconds passed
end)

Promise.async returns a Promise that can be used with :next or await in another async function, for example:

Promise.async(function(await)
    local data = await(Promise.json(http, "https://my-api"))
    return data.value * 200 -- "value" is a number
end):next(function(n)
    -- n is the result of the multiplication in the previous function
end)

Error handling:

Promise.async(function(await)
    -- second result from await is the error if rejected
    local data, err = await(Promise.reject("nope"))
    assert(err == "nope")
end)

Error handling with http/json:

Promise.async(function(await)
    local result, err = await(Promise.json(http, "https://httpbin.org/status/500"))
    assert(err == "unexpected status-code: 500")
end)

License

Yo dawg

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages