77--- @field wait fun ( self : self , timeout ?: integer , interval ?: integer ): any
88--- @field is_resolved fun ( self : self ): boolean
99--- @field is_rejected fun ( self : self ): boolean
10+ --- @field await fun ( self : self ): any
1011--- @field _resolved boolean
1112--- @field _value any
1213--- @field _error any
1314--- @field _then_callbacks fun ( value : any )[]
1415--- @field _catch_callbacks fun ( err : any )[]
16+ --- @field _coroutines thread[]
1517local Promise = {}
1618Promise .__index = Promise
1719
20+ --- Resume waiting coroutines with result
21+ --- @param coroutines thread[]
22+ --- @param value any
23+ --- @param err any
24+ local function resume_coroutines (coroutines , value , err )
25+ for _ , co in ipairs (coroutines ) do
26+ vim .schedule (function ()
27+ if coroutine.status (co ) == ' suspended' then
28+ coroutine.resume (co , value , err )
29+ end
30+ end )
31+ end
32+ end
33+
1834--- Create a waitable promise that can be resolved or rejected later
1935--- @generic T
2036--- @return Promise<T>
@@ -25,6 +41,7 @@ function Promise.new()
2541 _error = nil ,
2642 _then_callbacks = {},
2743 _catch_callbacks = {},
44+ _coroutines = {},
2845 }, Promise )
2946 return self
3047end
@@ -45,6 +62,9 @@ function Promise:resolve(value)
4562 for _ , callback in ipairs (self ._then_callbacks ) do
4663 schedule_then (callback , value )
4764 end
65+
66+ resume_coroutines (self ._coroutines , value , nil )
67+
4868 return self
4969end
5070
@@ -64,6 +84,9 @@ function Promise:reject(err)
6484 for _ , callback in ipairs (self ._catch_callbacks ) do
6585 schedule_catch (callback , err )
6686 end
87+
88+ resume_coroutines (self ._coroutines , nil , err )
89+
6790 return self
6891end
6992
@@ -193,6 +216,39 @@ function Promise:is_rejected()
193216 return self ._resolved and self ._error ~= nil
194217end
195218
219+ --- Await the promise from within a coroutine
220+ --- This will yield the coroutine until the promise resolves or rejects
221+ --- @generic T
222+ --- @return T
223+ function Promise :await ()
224+ -- If already resolved, return immediately
225+ if self ._resolved then
226+ if self ._error then
227+ error (self ._error )
228+ end
229+ return self ._value
230+ end
231+
232+ -- Get the current coroutine
233+ local co = coroutine.running ()
234+ if not co then
235+ error (' await() can only be called from within a coroutine' )
236+ end
237+
238+ -- Register the coroutine to be resumed when promise settles
239+ table.insert (self ._coroutines , co )
240+
241+ -- Yield and wait for resume
242+ --- @diagnostic disable-next-line : await-in-sync
243+ local value , err = coroutine.yield ()
244+
245+ if err then
246+ error (err )
247+ end
248+
249+ return value
250+ end
251+
196252--- @generic T
197253--- @param obj T
198254--- @return _cast obj Promise<T>
@@ -211,4 +267,54 @@ function Promise.wrap(obj)
211267 end
212268end
213269
270+ --- Run an async function in a coroutine
271+ --- The function can use promise:await() to wait for promises
272+ --- @generic T
273+ --- @param fn fun (): T
274+ --- @return Promise<T>
275+ --- @return _cast T Promise<T>
276+ function Promise .async (fn )
277+ local promise = Promise .new ()
278+
279+ local co = coroutine.create (function ()
280+ local ok , result = pcall (fn )
281+ if not ok then
282+ promise :reject (result )
283+ else
284+ if Promise .is_promise (result ) then
285+ result
286+ :and_then (function (val )
287+ promise :resolve (val )
288+ end )
289+ :catch (function (err )
290+ promise :reject (err )
291+ end )
292+ else
293+ promise :resolve (result )
294+ end
295+ end
296+ end )
297+
298+ local ok , err = coroutine.resume (co )
299+ if not ok then
300+ promise :reject (err )
301+ end
302+
303+ return promise
304+ end
305+
306+ --- Wrap a function to run asynchronously
307+ --- Takes a function and returns a wrapped version that returns a Promise
308+ --- @generic T
309+ --- @param fn fun ( ... ): T
310+ --- @return fun ( ... ): Promise<T>
311+ function Promise .async_wrap (fn )
312+ return function (...)
313+ local args = { ... }
314+ return Promise .async (function ()
315+ return fn (unpack (args ))
316+ end )
317+ end
318+ end
319+
214320return Promise
0 commit comments