Skip to content

Commit fc6049a

Browse files
committed
feat: add error handling
1 parent cb1e229 commit fc6049a

File tree

2 files changed

+165
-360
lines changed

2 files changed

+165
-360
lines changed

README.md

Lines changed: 62 additions & 293 deletions
Original file line numberDiff line numberDiff line change
@@ -1,337 +1,106 @@
1-
# [Lua Async Await](https://ms-jpq.github.io/lua-async-await)
1+
# [Lua Async Await](https://github.com/nvim-java/lua-async-await)
22

3-
Async Await in [90 lines](https://github.com/ms-jpq/lua-async-await/blob/master/lua/async.lua) of code.
3+
This is basically [ms-jpq/lua-async-await](https://github.com/ms-jpq/lua-async-await) but with Promise like error handling
44

5-
Originally written for Neovim, because it uses the same `libuv` eventloop from NodeJS.
5+
Refer the original repository for more comprehensive documentation on how all this works
66

7-
**Works for any LUA code**
7+
## Why?
88

9-
## Special Thanks
9+
A Language Server command response contains two parameters. `error` & `response`. If the error is present
10+
then the error should be handled.
1011

11-
[svermeulen](https://github.com/svermeulen) for fixing [inability to return functions](https://github.com/ms-jpq/lua-async-await/issues/2).
12-
13-
## Preface
14-
15-
This tutorial assumes that you are familiar with the concept of `async` `await`
16-
17-
You will also need to read through the [first 500 words](https://www.lua.org/pil/9.1.html) of how coroutines work in lua.
18-
19-
## [Luv](https://github.com/luvit/luv)
20-
21-
Neovim use [libuv](https://github.com/libuv/libuv) for async, the same monster that is the heart of NodeJS.
22-
23-
The `libuv` bindings are exposed through `luv` for lua, this is accessed using `vim.loop`.
24-
25-
Most of the `luv` APIs are similar to that of NodeJS, ie in the form of
26-
27-
`API :: (param1, param2, callback)`
28-
29-
Our goal is avoid the dreaded calback hell.
30-
31-
## Preview
12+
Ex:-
3213

3314
```lua
34-
local a = require "async"
35-
36-
local do_thing = a.sync(function (val)
37-
local o = a.wait(async_func())
38-
return o + val
39-
end)
40-
41-
local main = a.sync(function ()
42-
local thing = a.wait(do_thing()) -- composable!
43-
44-
local x = a.wait(async_func())
45-
local y, z = a.wait_all{async_func(), async_func()}
46-
end)
47-
48-
main()
49-
```
50-
51-
## [Coroutines](https://www.lua.org/pil/9.1.html)
52-
53-
If you don't know how coroutines work, go read the section on generators on [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators).
54-
55-
It is in js, but the idea is identical, and the examples are much better.
56-
57-
---
58-
59-
Here is an example of coroutines in Lua:
60-
61-
Note that in Lua code `coroutine` is not a coroutine, it is an namespace.
62-
63-
To avoid confusion, I will follow the convention used in the Lua book, and use `thread` to denote coroutines in code.
64-
65-
```lua
66-
local co = coroutine
67-
68-
local thread = co.create(function ()
69-
local x, y, z = co.yield(something)
70-
return 12
71-
end)
72-
73-
local cont, ret = co.resume(thread, x, y, z)
15+
self.client.request('workspace/executeCommand', cmd_info, function(err, res)
16+
if err then
17+
log.error(command .. ' failed! arguments: ', arguments, ' error: ', err)
18+
else
19+
log.debug(command .. ' success! response: ', res)
20+
end
21+
end, buffer)
7422
```
7523

76-
---
77-
78-
Notice the similarities with `async` `await`
79-
80-
In both `async` `await` and `coroutines`, the LHS of the assignment statements receives values from the RHS.
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.
8127

82-
This is how it works in all synchronous assignments. Except, we can defer the transfer of the values from RHS.
28+
So I wanted Promise like error handling but without Promises.
8329

84-
The idea is that we will make RHS send values to LHS, when RHS is ready.
30+
## How to use
8531

86-
## Synchronous Coroutines
87-
88-
To warm up, we will do a synchronous version first, where the RHS is always ready.
89-
90-
Here is how you send values to a coroutine:
32+
Assume following is the asynchronous API
9133

9234
```lua
93-
co.resume(thread, x, y, z)
94-
```
35+
local function lsp_request(callback)
36+
local timer = vim.loop.new_timer()
9537

96-
---
38+
assert(timer)
9739

98-
The idea is that we will repeat this until the coroutine has been "unrolled"
99-
100-
```lua
101-
local pong = function (thread)
102-
local nxt = nil
103-
nxt = function (cont, ...)
104-
if not cont
105-
then return ...
106-
else return nxt(co.resume(thread, ...))
107-
end
108-
end
109-
return nxt(co.resume(thread))
40+
timer:start(2000, 0, function()
41+
-- First parameter is the error
42+
callback('something went wrong', nil)
43+
end)
11044
end
11145
```
11246

113-
---
47+
### When no error handler defined
11448

115-
if we give `pong` some coroutine, it will recursively run the coroutine until completion
49+
This is how you can call this asynchronous API without a callback
11650

11751
```lua
118-
local thread = co.create(function ()
119-
local x = co.yield(1)
120-
print(x)
121-
local y, z = co.yield(2, 3)
122-
print(y)
123-
end)
124-
125-
pong(thread)
52+
M.sync(function()
53+
local response = M.wait_handle_error(M.wrap(lsp_request)())
54+
end).run()
12655
```
12756

128-
We can expect to see `1`, `2 3` printed.
129-
130-
## [Thunk](https://stackoverflow.com/questions/2641489/what-is-a-thunk)
131-
132-
Once you understand how the synchronous `pong` works, we are super close!
133-
134-
But before we make the asynchronous version, we need to learn one more simple concept.
57+
Result:
13558

136-
For our purposes a `Thunk` is function whose purpose is to invoke a callback.
137-
138-
i.e. It adds a transformation of `(arg, callback) -> void` to `arg -> (callback -> void) -> void`
139-
140-
```lua
141-
local read_fs = function (file)
142-
local thunk = function (callback)
143-
fs.read(file, callback)
144-
end
145-
return thunk
146-
end
14759
```
148-
149-
---
150-
151-
This too, is a process that can be automated:
152-
153-
```lua
154-
local wrap = function (func)
155-
local factory = function (...)
156-
local params = {...}
157-
local thunk = function (step)
158-
table.insert(params, step)
159-
return func(unpack(params))
160-
end
161-
return thunk
162-
end
163-
return factory
164-
end
165-
166-
local thunk = wrap(fs.read)
167-
```
168-
169-
So why do we need this?
170-
171-
## Async Await
172-
173-
The answer is simple! We will use thunks for our RHS!
174-
175-
---
176-
177-
With that said, we will still need one more magic trick, and that is to make a `step` function.
178-
179-
The sole job of the `step` funciton is to take the place of the callback to all the thunks.
180-
181-
In essence, on every callback, we take 1 step forward in the coroutine.
182-
183-
```lua
184-
local pong = function (func, callback)
185-
assert(type(func) == "function", "type error :: expected func")
186-
local thread = co.create(func)
187-
local step = nil
188-
step = function (...)
189-
local stat, ret = co.resume(thread, ...)
190-
assert(stat, ret)
191-
if co.status(thread) == "dead" then
192-
(callback or function () end)(ret)
193-
else
194-
assert(type(ret) == "function", "type error :: expected func")
195-
ret(step)
196-
end
197-
end
198-
step()
199-
end
60+
Error executing luv callback:
61+
test6.lua:43: unhandled error test6.lua:105: something went wrong
62+
stack traceback:
63+
[C]: in function 'error'
64+
test6.lua:43: in function 'callback'
65+
test6.lua:130: in function <test6.lua:129>
20066
```
20167

202-
Notice that we also make pong call a callback once it is done.
203-
204-
---
205-
206-
We can see it in action here:
68+
### When error handler is defined
20769

20870
```lua
209-
local echo = function (...)
210-
local args = {...}
211-
local thunk = function (step)
212-
step(unpack(args))
213-
end
214-
return thunk
215-
end
216-
217-
local thread = co.create(function ()
218-
local x, y, z = co.yield(echo(1, 2, 3))
219-
print(x, y, z)
220-
local k, f, c = co.yield(echo(4, 5, 6))
221-
print(k, f, c)
71+
local main = M.sync(function()
72+
local response = M.wait_handle_error(M.wrap(lsp_request)())
22273
end)
223-
224-
pong(thread)
74+
.catch(function(err)
75+
print('error occurred ', err)
76+
end)
77+
.run()
22578
```
22679

227-
We can expect this to print `1 2 3` and `4 5 6`
228-
229-
Note, we are using a synchronous `echo` for illustration purposes. It doesn't matter when the `callback` is invoked. The whole mechanism is agnostic to timing.
80+
Result:
23081

231-
You can think of async as the more generalized version of sync.
232-
233-
You can run an asynchronous version in the last section.
234-
235-
## Await All
236-
237-
One more benefit of thunks, is that we can use them to inject arbitrary computation.
238-
239-
Such as joining together many thunks.
240-
241-
```lua
242-
local join = function (thunks)
243-
local len = table.getn(thunks)
244-
local done = 0
245-
local acc = {}
246-
247-
local thunk = function (step)
248-
if len == 0 then
249-
return step()
250-
end
251-
for i, tk in ipairs(thunks) do
252-
local callback = function (...)
253-
acc[i] = {...}
254-
done = done + 1
255-
if done == len then
256-
step(unpack(acc))
257-
end
258-
end
259-
tk(callback)
260-
end
261-
end
262-
return thunk
263-
end
26482
```
265-
266-
This way we can perform `await_all` on many thunks as if they are a single one.
267-
268-
## More Sugar
269-
270-
All this explicit handling of coroutines are abit ugly. The good thing is that we can completely hide the implementation detail to the point where we don't even need to require the `coroutine` namespace!
271-
272-
Simply wrap the coroutine interface with some friendly helpers
273-
274-
```lua
275-
local pong = function (func, callback)
276-
local thread = co.create(func)
277-
...
278-
end
279-
280-
local await = function (defer)
281-
return co.yield(defer)
282-
end
283-
284-
local await_all = function (defer)
285-
return co.yield(join(defer))
286-
end
83+
error occured test6.lua:105: something went wrong
28784
```
28885

289-
## Composable
290-
291-
At this point we are almost there, just one more step!
86+
### When nested
29287

29388
```lua
294-
local sync = wrap(pong)
295-
```
296-
297-
We `wrap` `pong` into a thunk factory, so that calling it is no different than yielding other thunks. This is how we can compose together our `async` `await`.
298-
299-
It's thunks all the way down.
300-
301-
## Tips and Tricks
302-
303-
In Neovim, we have something called `textlock`, which prevents many APIs from being called unless you are in the main event loop.
89+
local nested = M.sync(function()
90+
local response = M.wait_handle_error(M.wrap(lsp_request)())
91+
end)
30492

305-
This will prevent you from essentially modifying any Neovim states once you have invoked a `vim.loop` funciton, which run in a seperate loop.
93+
M.sync(function()
94+
M.wait_handle_error(nested.run)
95+
end)
96+
.catch(function(err)
97+
print('parent error handler ' .. err)
98+
end)
99+
.run()
100+
```
306101

307-
Here is how you break back to the main loop:
102+
Result:
308103

309-
```lua
310-
local main_loop = function (f)
311-
vim.schedule(f)
312-
end
313104
```
314-
315-
```lua
316-
a.sync(function ()
317-
-- do something in other loop
318-
a.wait(main_loop)
319-
-- you are back!
320-
end)()
105+
parent error handler test6.lua:105: test6.lua:105: something went wrong
321106
```
322-
323-
## Plugin!
324-
325-
I have bundle up this tutorial as a vim plugin, you can install it the usual way.
326-
327-
`Plug 'ms-jpq/lua-async-await', {'branch': 'neo'}`
328-
329-
and then call the test functions like so:
330-
331-
`:LuaAsyncExample`
332-
333-
`:LuaSyncExample`
334-
335-
`:LuaTextlockFail`
336-
337-
`:LuaTextLockSucc`

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy