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  }