{"id":24659095,"url":"https://github.com/sladkokotikov/loveasyncawait","last_synced_at":"2025-10-07T21:31:36.913Z","repository":{"id":269600171,"uuid":"907954648","full_name":"Sladkokotikov/loveAsyncAwait","owner":"Sladkokotikov","description":"async / await syntax with LÖVE2D and Lua with no dependencies, in just 32 lines of code","archived":false,"fork":false,"pushed_at":"2025-01-26T10:31:06.000Z","size":16,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"async","last_synced_at":"2025-04-01T17:08:55.875Z","etag":null,"topics":["async-await","love2d","lua"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sladkokotikov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2024-12-24T17:36:54.000Z","updated_at":"2025-01-14T10:14:13.000Z","dependencies_parsed_at":"2024-12-24T18:42:44.533Z","dependency_job_id":null,"html_url":"https://github.com/Sladkokotikov/loveAsyncAwait","commit_stats":null,"previous_names":["sladkokotikov/loveasyncawait"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Sladkokotikov/loveAsyncAwait","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sladkokotikov%2FloveAsyncAwait","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sladkokotikov%2FloveAsyncAwait/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sladkokotikov%2FloveAsyncAwait/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sladkokotikov%2FloveAsyncAwait/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sladkokotikov","download_url":"https://codeload.github.com/Sladkokotikov/loveAsyncAwait/tar.gz/refs/heads/async","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sladkokotikov%2FloveAsyncAwait/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278852938,"owners_count":26057409,"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","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["async-await","love2d","lua"],"created_at":"2025-01-26T02:31:44.919Z","updated_at":"2025-10-07T21:31:36.650Z","avatar_url":"https://github.com/Sladkokotikov.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LÖVE2D async / await\n\nMy naive implementation of async / await syntax with LÖVE2D and Lua with no dependencies, in just 32 lines of code, along with the thoughts and interesting things I discovered during the process.\n\n# Spoilers\n\n### Fire and forget, Delay\n```lua\nfunction printMessageAsync(a, delay, message)\n    a:waitSeconds(delay)\n    print(message)\nend\n\nfireAndForget(printMessageAsync, 1, \"My! My! Time Flies!\") -- prints \"My! My! Time Flies!\" in one second\n```\n\n### Await other async operations and get results, create anonymous async functions\n```lua\nfunction doubleNumberAsync(a, num)\n    a:waitSeconds(0.2)\n    return num * 2\nend\n\nfunction multiplyByFourAsync(a, num)\n    local x2 = a:wait(doubleNumberAsync, num)\n    local x4 = a:wait(doubleNumberAsync, x2)\n    return x4\nend\n\nfireAndForget(function(a) \n    print(a:wait(multiplyByFourAsync, 4)) \nend)\n```\n\n### Awaiting completion source with result\n```lua\nfunction waitForClickAsync(a)\n    completionSource = a\n    local x, y, button = a:waitSource()\n    print(\"Clicked!\")\n    completionSource = nil\nend\n\nfunction love.mousepressed(x, y, button)\n    if completionSource then\n        completionSource.complete(x, y, button)\n    end\nend\n\nfireAndForget(waitForClickAsync)\n```\n\n# Here be downsides.\n\n- To make method `async`, you need to add **first** argument called `a` \n(in order to use beautiful `a:wait` syntax, of course), and forget / a:wait them without parentheses 👻\n\n\n    There are some ugly ways to use `a:` as an upvalue or as a local variable, but they use `debug` table, which is too tricky even for me. Also I play fair, and there is no file preprocessing. This is pure Lua for LÖVE2D (tested for version 11.5 with Lua 5.1).\n\n\n- Also, if you are planning to a:waitSeconds (and a:waitFrames, which is easy to implement as well) you need a ticker. I provided the simplest one in [async.lua](async.lua) file\n\n\n- There is no support for `a:waitAll` or `a:waitAny` at the moment, I just didn't need them, but I think it's easy to implement.\n\n\n- This is more of a \"proof of concept\" thing, minimal but working implementation that looks good enough.\n\n\n- I don't know if it affects optimization at a large scale! New coroutine is created every `fireAndForget` call, so don't recommend it as often as `update`, for example.\n\n\n# Why though?\n\nI fell in LÖVE with Lua, but I missed `async` keyword so much! I looked up and found [this repository](https://github.com/ms-jpq/lua-async-await), got scared and closed the tab. And in few months I took my own attempt, and it seems like I succeeded\n\n\n# I hope you know how to use...\n[Lua coroutines](https://www.lua.org/manual/5.1/manual.html#5.2) and other languages `async/await` syntax.\n\n# Core idea\n\nI remember thinking \"_What if a coroutine resumed itself, but at the right moment?_\". \n\nThat's it!\n\n\nLet's start with `fireAndForget`:\n\n```lua\nfunction fireAndForget(fn, ...) -- 1\n    local asyncState = setmetatable({}, A) -- 2\n    local args = {...}\n    asyncState.co = -- 5\n        coroutine.create( -- 4\n            function() -- 3\n                fn(asyncState, unpack(args))\n            end\n    )\n    coroutine.resume(asyncState.co) -- 6\nend\n```\n\n1. It accepts a callback and any number of arguments to be called with\n2. First, it creates a new async state (just a table) and ensures it has all the required `:wait` methods - thanks to the metatable\n3. Next, it creates a thunk - a function that will call given callback with given arguments, also passing async state as the first argument\n4. Next, we create a coroutine from a thunk...\n5. and cache it in async state!\n6. Finally, we resume the coroutine\n\n\nMagic! ✨\n\nIf a function doesn't use `a:wait`, it is just called synchronously. Boring.\n\nBut if it uses...\n\nLet's have a look at `a:waitSeconds` implementation.\n\n```lua\nfunction A:waitSeconds(delaySeconds) -- 1\n    table.insert(tickers, {delaySeconds, function() -- 3\n        coroutine.resume(self.co) -- 2\n    end})\n    coroutine.yield() -- 4\nend -- 5\n```\n1. Quite self explanatory\n2. We ensure that coroutine of current async state is resumed ...\n3. after some delay.\nRemember the core idea? Coroutine resumes itself at the right moment - in some seconds!\n4. And we yield. \n5. When given amount of seconds has passed, coroutine will be resumed, and we will exit from function\n\n\nMagic! ✨\n\n\nIn simple words, coroutines allow to stop execution and then go back to the stopping point, _nice of them_, so we will use exactly that.\n\nNext in line, `a:waitSource`. This allows us to resume the asynchronous function from another function, for example, on click.\n\n```lua\nfunction A:waitSource()\n    self.complete = function(...)\n        coroutine.resume(self.co, ...)\n    end\n    return coroutine.yield()\nend\n```\n\nThe idea remains the same: coroutine resumes itself at the right moment, in this case - whenever `complete` is called. When that happens, execution is resumed from `coroutine.yield` point, and we also return arguments of `complete` as completionSource result. \n\nVoilà!\n\nThe last one. The most intriguing one! `a:wait`\n\n```lua\nfunction A:wait(fn, ...)\n    return fn(self, ...)\nend\n```\n\nWhat? _Just one line?_\n**Yes**. It just calls a function, passing all of the arguments and ensuring that the first argument is beautiful `asyncState`.\n\n\nThis is more than magic. \n\n\nSorcery! 🔮\n\n\n# The end!\n\n### Thanks for reading! I'm glad if you found it helpful.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsladkokotikov%2Floveasyncawait","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsladkokotikov%2Floveasyncawait","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsladkokotikov%2Floveasyncawait/lists"}