github.com/primecitizens/pcz/std@v0.2.1/ffi/js/async.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright 2023 The Prime Citizens 3 4 package js 5 6 import ( 7 "github.com/primecitizens/pcz/std/core/abi" 8 "github.com/primecitizens/pcz/std/core/assert" 9 "github.com/primecitizens/pcz/std/core/yield" 10 "github.com/primecitizens/pcz/std/ffi/js/async" 11 "github.com/primecitizens/pcz/std/ffi/js/bindings" 12 ) 13 14 // TODO: support batch awaiting (Promise.{allSettled, all, race}) 15 16 // ExecutorFunc defines the go function signature of a promise executor. 17 type ExecutorFunc[T any] func(arg T, this Any) (value, reason Ref, fulfilled bool) 18 19 type jsExecutorFunc = func(resolve, reject Func[Ref]) 20 21 // Executor is a helper for constructing new Promises in js. 22 // 23 // TODO: support handling multiple promises simultaneously. 24 type Executor[T any] struct { 25 // executor is the executor function to be called from js. 26 executor ExecutorFunc[T] 27 arg T 28 29 fn Func[jsExecutorFunc] 30 promise bindings.Ref 31 } 32 33 func (p *Executor[T]) Free() { 34 Free(p.fn.ref, p.promise) 35 } 36 37 // Reset prepares a new promise for use in js. 38 func (p *Executor[T]) Reset(arg T, fn ExecutorFunc[T]) Ref { 39 p.arg = arg 40 41 if p.executor == nil { 42 bindings.Free(p.promise) 43 p.executor = fn 44 } else if abi.FuncPCABIInternal(p.executor) != abi.FuncPCABIInternal(fn) { 45 p.Free() 46 p.fn.ref = bindings.Ref(Undefined) 47 p.executor = fn 48 } 49 50 p.promise = async.Promise(p.register().ref) 51 return Ref(p.promise) 52 } 53 54 func (p *Executor[T]) register() Func[jsExecutorFunc] { 55 if p == nil || p.executor == nil { 56 assert.Throw("nil", "context", "or", "executor") 57 } 58 59 if p.fn.ref != bindings.Ref(Undefined) { 60 return p.fn 61 } 62 63 p.fn = RegisterCallback[jsExecutorFunc](p, abi.FuncPCABIInternal(p.executor)) 64 return p.fn 65 } 66 67 // DispatchCallback implements CallbackDispatcher. 68 func (p *Executor[T]) DispatchCallback(targetPC uintptr, ctx *CallbackContext) { 69 args := ctx.Args() 70 if len(args) != 3 || targetPC != abi.FuncPCABIInternal(p.executor) { 71 assert.Throw("invalid", "promise", "callback", "call") 72 } 73 74 value, reason, fulfilled := p.executor( 75 p.arg, 76 Any{ 77 ref: bindings.Ref(args[0]), 78 }, 79 ) 80 81 if fulfilled { 82 Func[func(Any)]{ref: bindings.Ref(args[1])}.CallVoid(args[0], true, value) 83 } else { 84 Func[func(Any)]{ref: bindings.Ref(args[2])}.CallVoid(args[0], true, reason) 85 } 86 } 87 88 func NewResolvedPromise[ 89 T interface{ FromRef(Ref) T }, 90 ](data Ref) Promise[T] { 91 return Promise[T]{ 92 ref: async.Resolved(bindings.Ref(data)), 93 } 94 } 95 96 func NewRejectedPromise[ 97 T interface{ FromRef(Ref) T }, 98 ](reason Ref) Promise[T] { 99 return Promise[T]{ 100 ref: async.Rejected(bindings.Ref(reason)), 101 } 102 } 103 104 // TryAwait is a helper function to consume Promises returned 105 // from TryXxx functions. 106 // 107 // It calls p.Await(true) when ok is true. 108 func TryAwait[ 109 T interface{ FromRef(Ref) T }, 110 ]( 111 p Promise[T], exception Any, ok bool, 112 ) ( 113 value T, err Any, fulfilled bool, 114 ) { 115 if !ok { 116 err = exception 117 return 118 } 119 120 return p.Await(true) 121 } 122 123 type Promise[T interface{ FromRef(Ref) T }] struct { 124 ref bindings.Ref 125 } 126 127 func (p Promise[T]) FromRef(ref Ref) Promise[T] { 128 return Promise[T]{ref: bindings.Ref(ref)} 129 } 130 131 func (p Promise[T]) Ref() Ref { 132 return Ref(p.ref) 133 } 134 135 func (p Promise[T]) Once() Promise[T] { 136 bindings.Once(p.ref) 137 return p 138 } 139 140 func (p Promise[T]) Free() { 141 bindings.Free(p.ref) 142 } 143 144 // Then calls Promise.then(onfulfilled, onrejected) and 145 // if onfinally is not undefined/null value, call Promise.finally(onfinally). 146 // 147 // NOTE: It is generally discouraged to use this function for callbacks to Go code. 148 func (p Promise[T]) Then(onfulfilled, onrejected Func[func(Ref)], onfinally Func[func()]) Promise[T] { 149 async.Then(onfulfilled.ref, onrejected.ref, onfinally.ref) 150 return p 151 } 152 153 // Await works like `await` 154 // 155 // When take is true, it frees the Promise p from the application heap. 156 // 157 //go:nosplit 158 func (p Promise[T]) Await(take bool) (value T, err Any, fulfilled bool) { 159 var ( 160 ref Ref 161 ok bool 162 ) 163 164 if bindings.Ref(True) == async.ShouldWait( 165 bindings.Ref(Bool(take)), p.ref, Pointer(&ok), Pointer(&ref), 166 ) { 167 yield.Thread() 168 // notified from js as promise finished 169 ok = bindings.Ref(True) == async.TakeWaited(bindings.Ref(ref), Pointer(&ref)) 170 } 171 172 if ok { 173 return value.FromRef(ref), err, true 174 } 175 176 return value, err.FromRef(ref), false 177 } 178 179 func (p Promise[T]) MustAwait(take bool) T { 180 val, err, ok := p.Await(take) 181 if !ok { 182 // TODO: log error 183 err.Free() 184 assert.Throw("promise", "not", "fulfilled") 185 } 186 187 return val 188 }