forked from nvim-lua/plenary.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasync.lua
More file actions
180 lines (151 loc) · 4.34 KB
/
async.lua
File metadata and controls
180 lines (151 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
local co = coroutine
local uv = vim.loop
local M = {}
--- WIP idle stuff
local thread_loop = function(thread, callback)
local idle = uv.new_idle()
idle:start(function()
local success = co.resume(thread)
assert(success, "Coroutine failed")
if co.status(thread) == "dead" then
idle:stop()
callback()
end
end)
end
-- use with wrap
local execute = function(future, callback)
assert(type(future) == "function", "type error :: expected func")
local thread = co.create(future)
local step
step = function(...)
local res = {co.resume(thread, ...)}
local stat = res[1]
local ret = {select(2, unpack(res))}
assert(stat, string.format("The coroutine failed with this message: %s", ret[1]))
if co.status(thread) == "dead" then
(callback or function() end)(unpack(ret))
else
assert(#ret == 1, "expected a single return value")
local returned_future = ret[1]
assert(type(returned_future) == "function", "type error :: expected func")
returned_future(step)
end
end
step()
end
-- use with CPS function, creates future factory
-- must have argc for arity checking
M.wrap = function(func, argc)
assert(type(func) == "function", "type error :: expected func, got " .. type(func))
assert(type(argc) == "number" or argc == "vararg", "expected argc to be a number or string literal 'vararg'")
return function(...)
local params = {...}
local function future(step)
if step then
if type(argc) == "number" then
params[argc] = step
else
table.insert(params, step) -- change once not optional
end
return func(unpack(params))
else
return co.yield(future)
end
end
return future
end
end
--- WIP
local thread_loop_async = M.wrap(thread_loop, 2)
-- many futures -> single future
M.join = M.wrap(function(futures, step)
local len = #futures
local results = {}
local done = 0
if len == 0 then
return step(results)
end
for i, future in ipairs(futures) do
assert(type(future) == "function", "type error :: future must be function")
local callback = function(...)
results[i] = {...} -- should we set this to a table
done = done + 1
if done == len then
-- step(unpack(results))
step(results) -- should we unpack?
end
end
future(callback)
end
end, 2)
--- use this over running a future by calling it with no callback argument because it is more explicit
M.run = function(future, callback)
future(callback or function() end)
end
M.run_all = function(futures, callback)
M.run(M.join(futures), callback)
end
M.await = function(future)
return future(nil)
end
M.await_all = function(futures)
assert(type(futures) == "table", "type error :: expected table")
return M.await(M.join(futures))
end
-- suspend co-routine, call function with its continuation (like call/cc)
M.suspend = co.yield
M.async = function(func)
return function(...)
local args = {...}
local function future(step)
if step == nil then
return func(unpack(args))
else
execute(future, step)
end
end
return future
end
end
-- converts an async function to callback based function
M.convert = function(async_func)
return function(...)
local args = {...}
local callback = table.remove(args)
assert(type(callback) == "function" or type(callback) == "nil", "type error :: expected function as last vararg")
M.run(async_func(unpack(args)), callback)
end
end
M.future = function(func)
return M.async(func)()
end
--- WIP
local execute_loop = M.async(function(func, callback)
assert(type(func) == "function", "type error :: expected func")
local thread = co.create(func)
local _step
_step = function(...)
local res = {co.resume(thread, ...)}
local stat = res[1]
local ret = {select(2, unpack(res))}
assert(stat, "Status should be true")
if co.status(thread) == "dead" then
(callback or function() end)(unpack(ret))
else
assert(#ret == 1, "expected a single return value")
assert(type(ret[1]) == "function", "type error :: expected func")
-- yield before calling the next one
co.yield()
ret[1](_step)
end
end
local step = function()
thread_loop(co.create(_step))
end
step()
end)
--- WIP
--- because idle is a bad name
M.spawn = M.wrap(execute_loop, 2)
return M