{"id":18991254,"url":"https://github.com/mt-mods/promise","last_synced_at":"2026-04-14T10:30:17.022Z","repository":{"id":162990431,"uuid":"626278575","full_name":"mt-mods/promise","owner":"mt-mods","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-11T19:52:08.000Z","size":233,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-11T20:36:02.431Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mt-mods.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-11T06:35:36.000Z","updated_at":"2025-02-11T19:52:12.000Z","dependencies_parsed_at":"2024-01-09T08:26:45.510Z","dependency_job_id":"6feffab1-1a80-4e62-803d-fced85116ed3","html_url":"https://github.com/mt-mods/promise","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mt-mods%2Fpromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mt-mods%2Fpromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mt-mods%2Fpromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mt-mods%2Fpromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mt-mods","download_url":"https://codeload.github.com/mt-mods/promise/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240002351,"owners_count":19732184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T17:13:16.691Z","updated_at":"2026-04-14T10:30:16.969Z","avatar_url":"https://github.com/mt-mods.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"promise library for minetest\n\n![](https://github.com/mt-mods/promise/workflows/luacheck/badge.svg)\n![](https://github.com/mt-mods/promise/workflows/test/badge.svg)\n[![License](https://img.shields.io/badge/License-MIT-green.svg)](license.txt)\n[![Download](https://img.shields.io/badge/Download-ContentDB-blue.svg)](https://content.minetest.net/packages/mt-mods/promise)\n[![Coverage Status](https://coveralls.io/repos/github/mt-mods/promise/badge.svg?branch=master)](https://coveralls.io/github/mt-mods/promise?branch=master)\n\n# Overview\n\nFeatures:\n* Async event handling\n* Utilities for formspec, emerge_area, handle_async, http and minetest.after\n* async/await helpers (js example [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function))\n\n# Examples\n\n\nSimple promise and handling:\n```lua\n-- create promise\nlocal p = Promise.new(function(resolve, reject)\n    -- async operation here, mocked for this example\n    minetest.after(1, function()\n        resolve(\"result-from-a-long-operation\")\n    end)\nend)\n\n-- handle the result later\np:next(function(result)\n    assert(result == \"result-from-a-long-operation\")\nend)\n```\n\nChained async operations:\n```lua\nPromise.emerge_area(pos1, pos2):next(function()\n    -- delay a second before next operation\n    return Promise.after(1)\nend):next(function()\n    -- called after emerge + 1 second delay\nend)\n```\n\nWait for multiple http requests:\n```lua\nlocal http = minetest.request_http_api()\nlocal toJson = function(res) return res.json() end\n\nlocal p1 = Promise.http(http, \"http://localhost/x\"):next(toJson)\nlocal p2 = Promise.http(http, \"http://localhost/y\"):next(toJson)\n\nPromise.all(p1, p2):next(function(values)\n    local x = values[1]\n    local y = values[2]\nend)\n```\n\nWait for multiple async workers:\n```lua\nlocal fn = function(x,y)\n    return x*y\nend\n\nlocal p1 = Promise.handle_async(fn, 1, 1)\nlocal p2 = Promise.handle_async(fn, 2, 2)\nlocal p3 = Promise.handle_async(fn, 10, 2)\n\nPromise.all(p1, p2, p3):next(function(values)\n    assert(values[1] == 1)\n    assert(values[2] == 4)\n    assert(values[3] == 20)\nend)\n```\n\n# Api\n\n## `Promise.new(callback)`\n\nCreates a new promise\n\nExample:\n```lua\nlocal p = Promise.new(function(resolve, reject)\n    -- TODO: async operation and resolve(value) or reject(err)\nend)\n\n-- test if the value is a promise\nassert(p.is_promise == true) -- field value\nassert(Promise.is_promise(p)) -- function\n\np:then(function(result)\n    -- TODO: handle the result\nend):catch(function(err)\n    -- TODO: handle the error\nend)\n\np:finally(function()\n    -- always called after error or success\n    -- TODO: handle cleanup/common things here\nend)\n```\n\nAlternatively:\n```lua\n-- promise without callback\nlocal p = Promise.new()\n-- later on: resolve from outside\np:resolve(result)\n```\n\n**NOTE:** pass a `0` to the `error` function if you want to evaluate the error directly:\n\n```lua\nPromise.new(function()\n    error(\"nope\", 0)\nend):catch(function(err)\n    assert(err == \"nope\")\nend)\n```\n\nReference: https://www.lua.org/manual/5.3/manual.html#pdf-error\n\n## `Promise.resolve(value)`\n\nReturns an already resolved promise with given value\n\n## `Promise.reject(err)`\n\nReturns an already rejected promise with given error\n\n## `Promise.empty()`\n\nReturns an already resolved promise with a `nil` value\n\n## `Promise.all(...)`\n\nWait for all promises to finish\n\nExample:\n```lua\nlocal p1 = Promise.resolve(5)\nlocal p2 = Promise.resolve(10)\n\nPromise.all(p1, p2):next(function(values)\n    assert(#values == 2)\n    assert(values[1] == 5)\n    assert(values[2] == 10)\nend)\n```\n\n* Javascript version: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all\n\n## `Promise.race(...)`\n\nWait for the first promise to finish\n\nExample:\n```lua\nlocal p1 = Promise.resolve(5)\nlocal p2 = Promise.new()\n\nPromise.race(p1, p2):next(function(v)\n    assert(v == 5)\nend)\n```\n\nThe `race()` function can be used for timeouts, for example:\n\n```lua\nlocal p = Promise.new() -- never resolves\nlocal to = Promise.timeout(5) -- rejects after 5 seconds\n\nPromise.race(p, to):next(function(v)\n    -- process \"v\"\nend):catch(function(err)\n    -- timeout reached (err == \"timeout\")\nend)\n```\n\n* Javascript version: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race\n\n## `Promise.any(...)`\n\nReturns the first fulfilled promise or rejects if all promises reject.\n\n* Javascript version: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any\n\n## `Promise.after(delay, value?, err?)`\n\nReturns a delayed promise that resolves to given value or error\n\n## `Promise.timeout(delay)`\n\nReturns a promise that rejects with \"timeout\" after the given delay. Useful in comination with `Promise.race()`\n\n## `Promise.emerge_area(pos1, pos2?)`\n\nEmerges the given area and resolves afterwards\n\n## `Promise.formspec(playername, formspec, callback?)`\n\nFormspec shorthand / util\n\nExample:\n```lua\nPromise.formspec(playername, \"size[2,2]button_exit[0,0;2,2;mybutton;label]\")\n:next(function(fields)\n    -- formspec closed\n    assert(fields.mybutton == true)\nend)\n```\n\n**NOTE**: the promise only resolves if the player exits the formspec (with a `quit=\"true\"` value, a default in exit_buttons)\n\nExample with optional scroll/dropdown callbacks:\n```lua\nlocal callback = function(fields)\n    -- TODO: handle CHG, and other \"non-quit\" events here\nend\n\nPromise.formspec(playername, \"size[2,2]button_exit[0,0;2,2;mybutton;label]\", callback)\n:next(function(fields)\n    -- formspec closed\n    assert(fields.mybutton == true)\nend)\n```\n\n## `Promise.handle_async(fn, args...)`\n\nExecutes the function `fn` in the async environment with given arguments\n\n**NOTE:** This falls back to a simple function-call if the `minetest.handle_async` function isn't available.\n\n## `Promise.http(http, url, opts?)`\n\nHttp query\n\n* `http` The http instance returned from `minetest.request_http_api()`\n* `url` The url to call\n* `opts` Table with options:\n  * `method` The http method (default: \"GET\")\n  * `timeout` Timeout in seconds (default: 10)\n  * `data` Data to transfer, serialized as json if type is `table`\n  * `headers` table of additional headers\n\nExamples:\n```lua\nlocal http = minetest.request_http_api()\n\n-- call chuck norris api: https://api.chucknorris.io/ and expect json-response\nPromise.http(http, \"https://api.chucknorris.io/jokes/random\"):next(function(res)\n    return res.json()\nend):next(function(joke)\n    assert(type(joke.value) == \"string\")\nend)\n\n-- post json-payload with 10 second timeout and expect raw string-response (or error)\nPromise.http(http, \"http://localhost/stuff\", { method = \"POST\", timeout = 10, data = { x=123 } }):next(function(res)\n    return res.text()\nend):next(function(result)\n    assert(result)\nend):catch(function(res)\n    -- something went wrong with the http call itself (no response)\n    -- dump the raw http response (res.code, res.timeout)\n    print(dump(res))\nend)\n```\n\n## `Promise.json(http, url, opts?)`\n\nHelper function for `Promise.http` that parses a json response\n\nExample:\n```lua\n-- call chuck norris api: https://api.chucknorris.io/ and expect json-response\nPromise.json(http, \"https://api.chucknorris.io/jokes/random\"):next(function(joke)\n    assert(type(joke.value) == \"string\")\nend, function(err)\n    -- request not successful or response-status not 200\n    print(\"something went wrong while calling the api: \" .. err)\nend)\n```\n\n## `Promise.mods_loaded()`\n\nResolved on mods loaded (`minetest.register_on_mods_loaded`)\n\nExample:\n```lua\nPromise.mods_loaded():next(function()\n    -- stuff that runs when all mods are loaded\nend)\n```\n\n## `Promise.on_punch(pos, timeout?)`\n\nResolves when the node at `pos` is hit or throws an error if the timeout (in seconds, default: 5) is reached.\n\n## `Promise.dynamic_add_media(options)`\n\nDynamic media push\n\nExample:\n```lua\nPromise.dynamic_add_media({ filepath = \"world/image.png\", to_player = \"singleplayer\" })\n:next(function(name)\n    -- player callback\nend):catch(function()\n    -- error handling\nend)\n```\n\n**NOTE**: experimental, only works if the `to_player` property is set\n\n## `Promise.register_chatcommand(cmd, def)`\n\nChatcommand helper with wrappers for success and error.\nShows messages after the returned promise fails or succeeds and prints the error or success-value if the type is \"string\"\n\nUsage:\n\n```lua\nPromise.register_chatcommand(\"something\", {\n    description = \"Does something\",\n    func = function(name)\n        return Promise.new(function(resolve, reject)\n            resolve(\"processed 123 items\")\n            -- or: reject(\"something went wrong\")\n        end)\n    end\n})\n```\n\n# async/await with `Promise.async`\n\nSimilar to [javascripts](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) implementation async/await can be used in lua too with the help of [coroutines](https://www.lua.org/pil/9.1.html)\n\nExample: fetch a joke with async/await\n```lua\nPromise.async(function(await)\n    local joke = await(Promise.json(http, \"https://api.chucknorris.io/jokes/random\"))\n    assert(type(joke.value) == \"string\")\n    -- do stuff here with the joke\nend)\n```\n\nExample: sleep for a few seconds\n```lua\nPromise.async(function(await)\n    await(Promise.after(5))\n    -- 5 seconds passed\nend)\n```\n\n`Promise.async` returns a Promise that can be used with `:next` or `await` in another async function, for example:\n\n```lua\nPromise.async(function(await)\n    local data = await(Promise.json(http, \"https://my-api\"))\n    return data.value * 200 -- \"value\" is a number\nend):next(function(n)\n    -- n is the result of the multiplication in the previous function\nend)\n```\n\nError handling:\n```lua\nPromise.async(function(await)\n    -- second result from await is the error if rejected\n    local data, err = await(Promise.reject(\"nope\"))\n    assert(err == \"nope\")\nend)\n```\n\nError handling with http/json:\n```lua\nPromise.async(function(await)\n    local result, err = await(Promise.json(http, \"https://httpbin.org/status/500\"))\n    assert(err == \"unexpected status-code: 500\")\nend)\n```\n\n\n# License\n\n* Code: MIT (adapted from https://github.com/Billiam/promise.lua)\n\n\u003cdetails\u003e\n\n![Yo dawg](yo.jpg)\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmt-mods%2Fpromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmt-mods%2Fpromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmt-mods%2Fpromise/lists"}