Skip to content

Commit 03abf6f

Browse files
committed
feat: simplify APIs and re name the confusing terms
1 parent 645ad2b commit 03abf6f

File tree

9 files changed

+239
-208
lines changed

9 files changed

+239
-208
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
MIT License
33

4-
Copyright (c) 2020 whocares
4+
Copyright (c) 2024 s1n7ax
55

66
Permission is hereby granted, free of charge, to any person obtaining a copy
77
of this software and associated documentation files (the "Software"), to deal

README.md

+50-72
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,90 @@
1-
# [Lua Async Await](https://github.com./nvim-java/lua-async-await)
1+
# [Lua Async](https://github.com./nvim-java/lua-async)
22

3-
This is basically [ms-jpq/lua-async-await](https://github.com./ms-jpq/lua-async-await) but with Promise like error handling
3+
Synchronous like asynchronous for Lua.
44

5-
Refer the original repository for more comprehensive documentation on how all this works
5+
## What
66

7-
## Why?
7+
Take a look at before and after
88

9-
A Language Server command response contains two parameters. `error` & `response`. If the error is present
10-
then the error should be handled.
11-
12-
Ex:-
9+
**Before:**
1310

1411
```lua
15-
self.client.request('workspace/executeCommand', cmd_info, function(err, res)
12+
client.request('workspace/executeCommand', cmd_info, function(err, res)
1613
if err then
17-
log.error(command .. ' failed! arguments: ', arguments, ' error: ', err)
14+
log.error(err)
1815
else
19-
log.debug(command .. ' success! response: ', res)
16+
log.debug(res)
2017
end
2118
end, buffer)
2219
```
2320

24-
Promises are fine but chaining is annoying specially when you don't have arrow function like
25-
syntactic sugar. Moreover, at the time of this is writing, Lua language server generics typing
26-
is so primitive and cannot handle `Promise<Something>` like types.
21+
**After:**
2722

28-
So I wanted Promise like error handling but without Promises.
23+
```lua
24+
-- on error, statement will fail throwing an error just like any synchronous API
25+
local result = client.request('workspace/executeCommand', cmd_info, buffer)
26+
log.debug(result)
27+
```
2928

30-
## How to use
29+
## Why
30+
31+
Well, callback creates callback hell.
3132

32-
Assume following is the asynchronous API
33+
## How to use
3334

3435
```lua
35-
local function lsp_request(callback)
36+
local runner = require("async.runner")
37+
local wrap = require("async.wrap")
38+
local wait = require("async.waits.wait_with_error_handler")
39+
40+
local function success_async(callback)
3641
local timer = vim.loop.new_timer()
3742

3843
assert(timer)
3944

4045
timer:start(2000, 0, function()
4146
-- First parameter is the error
42-
callback('something went wrong', nil)
47+
callback(nil, "hello world")
4348
end)
4449
end
45-
```
46-
47-
### When no error handler defined
48-
49-
This is how you can call this asynchronous API without a callback
5050

51-
```lua
52-
local M = require('sync')
53-
54-
M.sync(function()
55-
local response = M.wait_handle_error(M.wrap(lsp_request)())
56-
end).run()
57-
```
58-
59-
Result:
60-
61-
```
62-
Error executing luv callback:
63-
test6.lua:43: unhandled error test6.lua:105: something went wrong
64-
stack traceback:
65-
[C]: in function 'error'
66-
test6.lua:43: in function 'callback'
67-
test6.lua:130: in function <test6.lua:129>
68-
```
69-
70-
### When error handler is defined
51+
local function fail_async(callback)
52+
local timer = vim.loop.new_timer()
7153

72-
```lua
73-
local M = require('sync')
54+
assert(timer)
7455

75-
local main = M.sync(function()
76-
local response = M.wait_handle_error(M.wrap(lsp_request)())
77-
end)
78-
.catch(function(err)
79-
print('error occurred ', err)
56+
timer:start(2000, 0, function()
57+
-- First parameter is the error
58+
callback("something went wrong", nil)
8059
end)
81-
.run()
82-
```
60+
end
8361

84-
Result:
62+
local function log(message)
63+
vim.print(os.date("%H:%M:%S") .. " " .. message)
64+
end
8565

86-
```
87-
error occurred test6.lua:105: something went wrong
88-
```
66+
vim.cmd.messages("clear")
8967

90-
### When nested
68+
local nested = runner(function()
69+
local success_sync = wrap(success_async)
70+
local fail_sync = wrap(fail_async)
9171

92-
```lua
93-
local M = require('sync')
72+
local success_result = wait(success_sync())
73+
-- here we get the result because there is no error
74+
log("success_result is: " .. success_result)
9475

95-
local nested = M.sync(function()
96-
local response = M.wait_handle_error(M.wrap(lsp_request)())
76+
-- following is going to fail and error will get caught by
77+
-- the parent runner function's 'catch'
78+
wait(fail_sync())
9779
end)
9880

99-
M.sync(function()
100-
M.wait_handle_error(nested.run)
101-
end)
81+
runner(function()
82+
log("starting the execution")
83+
-- just wait for nested runner to complete the execution
84+
wait(nested.run)
85+
end)
10286
.catch(function(err)
103-
print('parent error handler ' .. err)
87+
log("parent error handler " .. err)
10488
end)
10589
.run()
10690
```
107-
108-
Result:
109-
110-
```
111-
parent error handler test6.lua:105: test6.lua:105: something went wrong
112-
```

lua/async/runner.lua

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
local wrap = require("async.wrap")
2+
local co = coroutine
3+
4+
---Runs the given function
5+
---@param func fun(): any
6+
---@return { run: function, catch: fun(error_handler: fun(error: any)) }
7+
local function runner(func)
8+
local m = {
9+
error_handler = nil,
10+
}
11+
12+
local async_thunk_factory = wrap(function(handler, parent_handler_callback)
13+
assert(type(handler) == "function", "type error :: expected func")
14+
local thread = co.create(handler)
15+
local step = nil
16+
17+
step = function(...)
18+
local ok, thunk = co.resume(thread, ...)
19+
20+
-- when an error() is thrown after co-routine is resumed, obviously further
21+
-- processing stops, and resume returns ok(false) and thunk(error) returns
22+
-- the error message
23+
if not ok then
24+
if m.error_handler then
25+
m.error_handler(thunk)
26+
return
27+
end
28+
29+
if parent_handler_callback then
30+
parent_handler_callback(thunk)
31+
return
32+
end
33+
34+
error("unhandled error " .. thunk)
35+
end
36+
37+
assert(ok, thunk)
38+
if co.status(thread) == "dead" then
39+
if parent_handler_callback then
40+
parent_handler_callback(thunk)
41+
end
42+
else
43+
assert(type(thunk) == "function", "type error :: expected func")
44+
thunk(step)
45+
end
46+
end
47+
48+
step()
49+
50+
return m
51+
end)
52+
53+
m.run = async_thunk_factory(func)
54+
55+
m.catch = function(error_handler)
56+
m.error_handler = error_handler
57+
return m
58+
end
59+
60+
return m
61+
end
62+
63+
return runner

lua/async/waits/wait.lua

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
local co = coroutine
2+
3+
---Waits for async function to be completed
4+
---@generic T
5+
---@param defer fun(callback: fun(result: T))
6+
---@return T
7+
local function wait(defer)
8+
assert(type(defer) == "function", "type error :: expected func")
9+
return co.yield(defer)
10+
end
11+
12+
return wait

lua/async/waits/wait_all.lua

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
local co = coroutine
2+
3+
local function __join(thunks)
4+
local len = #thunks
5+
local done = 0
6+
local acc = {}
7+
8+
local thunk = function(step)
9+
if len == 0 then
10+
return step()
11+
end
12+
for i, tk in ipairs(thunks) do
13+
assert(type(tk) == "function", "thunk must be function")
14+
local callback = function(...)
15+
acc[i] = { ... }
16+
done = done + 1
17+
if done == len then
18+
step(unpack(acc))
19+
end
20+
end
21+
tk(callback)
22+
end
23+
end
24+
return thunk
25+
end
26+
27+
---Waits for list of async calls to be completed
28+
---@param defer fun(callback: fun(result: any))
29+
---@return any[]
30+
local function wait_all(defer)
31+
assert(type(defer) == "table", "type error :: expected table")
32+
return co.yield(__join(defer))
33+
end
34+
35+
return wait_all
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
local co = coroutine
2+
3+
---Waits for async function to be completed but considers first parameter as
4+
---error
5+
---@generic T
6+
---@param defer fun(callback: fun(result: T))
7+
---@return T
8+
local function wait(defer)
9+
assert(type(defer) == "function", "type error :: expected func")
10+
11+
local err, value = co.yield(defer)
12+
13+
if err then
14+
error(err)
15+
end
16+
17+
return value
18+
end
19+
20+
return wait

lua/async/wrap.lua

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---Make the given function a promise automatically assuming the callback
2+
---function is the last argument
3+
---@generic T
4+
---@param func fun(..., callback: fun(result: T)): any
5+
---@return fun(...): T
6+
local function wrap(func)
7+
assert(type(func) == "function", "type error :: expected func")
8+
9+
local factory = function(...)
10+
local params = { ... }
11+
local thunk = function(step)
12+
table.insert(params, step)
13+
return func(unpack(params))
14+
end
15+
return thunk
16+
end
17+
return factory
18+
end
19+
20+
return wrap

0 commit comments

Comments
 (0)