Skip to content

Commit 6df5def

Browse files
authored
Merge pull request #721 from hrsh7th/promise-on-unhandled-rejection
Add Promise.on_unhandled_rejection
2 parents c3d1702 + 7bc5410 commit 6df5def

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

autoload/vital/__vital__/Async/Promise.vim

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let s:NOOP = funcref('s:noop')
3838

3939
let s:PROMISE = {
4040
\ '_state': s:PENDING,
41+
\ '_has_floating_child': v:false,
4142
\ '_children': [],
4243
\ '_fulfillments': [],
4344
\ '_rejections': [],
@@ -90,6 +91,9 @@ function! s:_publish(promise, ...) abort
9091
endif
9192

9293
if empty(a:promise._children)
94+
if settled == s:REJECTED && !a:promise._has_floating_child
95+
call s:_on_unhandled_rejection(a:promise._result)
96+
endif
9397
return
9498
endif
9599

@@ -270,15 +274,22 @@ function! s:wait(promise, ...) abort
270274
endif
271275
endfunction
272276

277+
let s:_on_unhandled_rejection = s:NOOP
278+
function! s:on_unhandled_rejection(on_unhandled_rejection) abort
279+
let s:_on_unhandled_rejection = a:on_unhandled_rejection
280+
endfunction
281+
273282
function! s:_promise_then(...) dict abort
274283
let parent = self
275284
let state = parent._state
276285
let child = s:new(s:NOOP)
277286
let l:Res = a:0 > 0 ? a:1 : v:null
278287
let l:Rej = a:0 > 1 ? a:2 : v:null
279288
if state == s:FULFILLED
289+
let parent._has_floating_child = v:true
280290
call s:Later.call(funcref('s:_invoke_callback', [state, child, Res, parent._result]))
281291
elseif state == s:REJECTED
292+
let parent._has_floating_child = v:true
282293
call s:Later.call(funcref('s:_invoke_callback', [state, child, Rej, parent._result]))
283294
else
284295
call s:_subscribe(parent, child, Res, Rej)

doc/vital/Async/Promise.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,20 @@ wait({promise}[, {options}]) *Vital.Async.Promise.wait()*
377377
call Promise.wait(p, 1000)
378378
" Is equivalent to call Promise.wait(p, {'timeout': 1000})
379379
>
380+
on_unhandled_rejection({callback}) *Vital.Async.Promise.on_unhandled_rejection*
381+
382+
Set callback to catch all unhandled rejected promise's result.
383+
If {callback} throws error, |Async.Promise| does not handle it.
384+
385+
The {callback} is |Funcref|, it's argument can be unhandled thrown error or unhandled rejected value.
386+
387+
Note:
388+
This callback will called even if you using |Vital.Async.Promise.wait()|.
389+
If you want to clear callback, you can use following codes.
390+
391+
>
392+
call Promise.on_unhandled_rejection(Promise.noop)
393+
<
380394

381395
is_promise({value}) *Vital.Async.Promise.is_promise()*
382396

test/Async/Promise.vimspec

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,89 @@ Describe Async.Promise
804804
endif
805805
End
806806
End
807+
808+
Describe .on_unhandled_rejection
809+
810+
After each
811+
call P.on_unhandled_rejection(P.noop)
812+
End
813+
814+
It should call when promise throw error but unhandled
815+
let l = l:
816+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
817+
818+
let p = P.resolve().then({ -> execute('throw "error"') })
819+
call s:wait_has_key(l, 'result')
820+
Assert HasKey(result, 'exception')
821+
Assert HasKey(result, 'throwpoint')
822+
End
823+
824+
It should call when promise rejected but unhandled
825+
let l = l:
826+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
827+
828+
let p = P.reject({ 'error': 'error' })
829+
call s:wait_has_key(l, 'result')
830+
Assert HasKey(result, 'error')
831+
Assert Equals(result.error, 'error')
832+
End
833+
834+
It should call when promise does not catch with finally
835+
let l = l:
836+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
837+
838+
let p = P.resolve().then({ -> execute('throw "error"') }).finally({ -> {} })
839+
call s:wait_has_key(l, 'result')
840+
Assert HasKey(result, 'exception')
841+
Assert HasKey(result, 'throwpoint')
842+
End
843+
844+
It should call when promise does not catch with children
845+
let l = l:
846+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
847+
848+
let p = P.resolve().then({ -> execute('throw "error"') }).then({ -> {} })
849+
call s:wait_has_key(l, 'result')
850+
Assert HasKey(result, 'exception')
851+
Assert HasKey(result, 'throwpoint')
852+
End
853+
854+
It should call when promise does not catch with wait
855+
let l = l:
856+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
857+
858+
let p = P.resolve().then({ -> execute('throw "error"') }).then({ -> {} })
859+
let [_, error] = P.wait(p)
860+
Assert Equals(error, result)
861+
End
862+
863+
It should not call when catched rejected promise
864+
let l = l:
865+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
866+
867+
let p = P.reject({ 'error': 'error' }).catch({ -> {} })
868+
call P.wait(Wait(100))
869+
Assert KeyNotExists(l, 'result')
870+
End
871+
872+
It should not call when catched thrown error
873+
let l = l:
874+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
875+
876+
let p = P.resolve().then({ -> execute('throw "error"') }).catch({ -> {} })
877+
call P.wait(Wait(100))
878+
Assert KeyNotExists(l, 'result')
879+
End
880+
881+
It should not call when promise does not throw error
882+
let l = l:
883+
call P.on_unhandled_rejection({ result -> extend(l, { 'result': result }) })
884+
885+
let p = P.resolve().then({ -> { 'success': 'success' } })
886+
call P.wait(Wait(100))
887+
Assert KeyNotExists(l, 'result')
888+
End
889+
End
807890
End
808891

809892
" vim:et ts=2 sts=2 sw=2 tw=0:

0 commit comments

Comments
 (0)