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
201 lines (169 loc) · 5.25 KB
/
async.lua
File metadata and controls
201 lines (169 loc) · 5.25 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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
local co = coroutine
local errors = require('plenary.errors')
local traceback_error = errors.traceback_error
local M = {}
---@class Future
---Something that will give a value when run
---Executes a future with a callback when it is done
---@param future Future: the future to execute
---@param callback function: the callback to call when done
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))}
if not stat then
error(string.format("The coroutine failed with this message: %s", ret[1]))
end
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
---Creates an async function with a callback style function.
---@param func function: A callback style function to be converted. The last argument must be the callback.
---@param argc number: The number of arguments of func. Must be included.
---@return function: Returns an async function
M.wrap = function(func, argc)
if type(func) ~= "function" then
traceback_error("type error :: expected func, got " .. type(func))
end
if type(argc) ~= "number" and argc ~= "vararg" then
traceback_error("expected argc to be a number or string literal 'vararg'")
end
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
---Return a new future that when run will run all futures concurrently.
---@param futures table: the futures that you want to join
---@return Future: returns a 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] = {...}
done = done + 1
if done == len then
step(results)
end
end
future(callback)
end
end, 2)
---Returns a future that when run will select the first future that finishes
---@param futures table: The future that you want to select
---@return Future
M.select = M.wrap(function(futures, step)
local selected = false
for _, future in ipairs(futures) do
assert(type(future) == "function", "type error :: future must be function")
local callback = function(...)
if not selected then
selected = true
step(...)
end
end
future(callback)
end
end, 2)
---Use this to either run a future concurrently and then do something else
---or use it to run a future with a callback in a non async context
---@param future Future
---@param callback function
M.run = function(future, callback)
future(callback or function() end)
end
---Same as run but runs multiple futures
---@param futures table
---@param callback function
M.run_all = function(futures, callback)
M.run(M.join(futures), callback)
end
---Await a future, yielding the current function
---@param future Future
---@return any: returns the result of the future when it is done
M.await = function(future)
assert(type(future) == "function", "type error :: expected function to await")
return future(nil)
end
---Same as await but can await multiple futures.
---If the futures have libuv leaf futures they will be run concurrently
---@param futures table
---@return table: returns a table of results that each future returned. Note that if the future returns multiple values they will be packed into a table.
M.await_all = function(futures)
assert(type(futures) == "table", "type error :: expected table")
return M.await(M.join(futures))
end
---suspend a coroutine
M.suspend = co.yield
---create a async scope
M.scope = function(func)
M.run(M.future(func))
end
--- Future a :: a -> (a -> ())
--- turns this signature
--- ... -> Future a
--- into this signature
--- ... -> ()
M.void = function(async_func)
return function(...)
async_func(...)(function() end)
end
end
---creates an async function
---@param func function
---@return function: returns an async function
M.async = function(func)
if type(func) ~= "function" then
traceback_error("type error :: expected func, got " .. type(func))
end
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
---creates a future
---@param func function
---@return Future
M.future = function(func)
return M.async(func)()
end
---An async function that when awaited will await the scheduler to be able to call the api.
M.scheduler = M.wrap(vim.schedule, 1)
return M