Skip to content

Commit

Permalink
promise: run callbacks on resolution
Browse files Browse the repository at this point in the history
This would support ES Modules, since v8's interface returns a promise
if a module contains top-level await or imports.
  • Loading branch information
Rob Figueiredo committed Apr 17, 2021
1 parent 571ffb0 commit 36874e7
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 1 deletion.
8 changes: 8 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ func (c *Context) Global() *Object {
return &Object{v}
}

// PerformMicrotaskCheckpoint runs the default MicrotaskQueue until empty.
// This is used to make progress on Promises.
func (c *Context) PerformMicrotaskCheckpoint() {
c.register()
defer c.deregister()
C.IsolatePerformMicrotaskCheckpoint(c.iso.ptr)
}

// Close will dispose the context and free the memory.
// Access to any values assosiated with the context after calling Close may panic.
func (c *Context) Close() {
Expand Down
31 changes: 31 additions & 0 deletions promise.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,34 @@ func (p *Promise) Result() *Value {
val := &Value{ptr, p.ctx}
return val
}

// Then invokes the given function when the promise has been fulfilled, not rejected.
// The returned Promise resolves after the given function has finished execution.
func (p *Promise) Then(cb FunctionCallback) *Promise {
p.ctx.register()
defer p.ctx.deregister()
cbID := p.ctx.iso.registerCallback(cb)
ptr := C.PromiseThen(p.ptr, C.int(cbID))
return &Promise{&Object{&Value{ptr, p.ctx}}}
}

// Then2 invokes one of the given functions when the promise is fulfilled or rejected.
// The returned Promise resolves after the callback has finished execution.
func (p *Promise) Then2(onFulfilled, onRejected FunctionCallback) *Promise {
p.ctx.register()
defer p.ctx.deregister()
onFulfilledID := p.ctx.iso.registerCallback(onFulfilled)
onRejectedID := p.ctx.iso.registerCallback(onRejected)
ptr := C.PromiseThen2(p.ptr, C.int(onFulfilledID), C.int(onRejectedID))
return &Promise{&Object{&Value{ptr, p.ctx}}}
}

// Catch invokes the given function if the promise is rejected.
// The returned Promise resolves after the callback has finished execution.
func (p *Promise) Catch(cb FunctionCallback) *Promise {
p.ctx.register()
defer p.ctx.deregister()
cbID := p.ctx.iso.registerCallback(cb)
ptr := C.PromiseCatch(p.ptr, C.int(cbID))
return &Promise{&Object{&Value{ptr, p.ctx}}}
}
61 changes: 60 additions & 1 deletion promise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"rogchap.com/v8go"
)

func TestPromise(t *testing.T) {
func TestPromiseFulfilled(t *testing.T) {
t.Parallel()

iso, _ := v8go.NewIsolate()
Expand All @@ -25,6 +25,19 @@ func TestPromise(t *testing.T) {
t.Errorf("unexpected state for Promise, want Pending (0) got: %v", s)
}

var thenInfo *v8go.FunctionCallbackInfo
prom1thenVal := prom1.Then(func(info *v8go.FunctionCallbackInfo) *v8go.Value {
thenInfo = info
return nil
})
prom1then, _ := prom1thenVal.AsPromise()
if prom1then.State() != v8go.Pending {
t.Errorf("unexpected state for dependent Promise, want Pending got: %v", prom1then.State())
}
if thenInfo != nil {
t.Error("unexpected call of Then prior to resolving the promise")
}

val1, _ := v8go.NewValue(iso, "foo")
res1.Resolve(val1)

Expand All @@ -36,6 +49,20 @@ func TestPromise(t *testing.T) {
t.Errorf("expected the Promise result to match the resolve value, but got: %s", result)
}

if thenInfo == nil {
t.Errorf("expected Then to be called, was not")
}
if len(thenInfo.Args()) != 1 || thenInfo.Args()[0].String() != "foo" {
t.Errorf("expected promise to be called with [foo] args, was: %+v", thenInfo.Args())
}
}

func TestPromiseRejected(t *testing.T) {
t.Parallel()

iso, _ := v8go.NewIsolate()
ctx, _ := v8go.NewContext(iso)

res2, _ := v8go.NewPromiseResolver(ctx)
val2, _ := v8go.NewValue(iso, "Bad Foo")
res2.Reject(val2)
Expand All @@ -44,4 +71,36 @@ func TestPromise(t *testing.T) {
if s := prom2.State(); s != v8go.Rejected {
t.Fatalf("unexpected state for Promise, want Rejected (2) got: %v", s)
}

var thenInfo *v8go.FunctionCallbackInfo
var then2Fulfilled, then2Rejected bool
prom2.
Catch(func(info *v8go.FunctionCallbackInfo) *v8go.Value {
thenInfo = info
return nil
}).
Then2(
func(_ *v8go.FunctionCallbackInfo) *v8go.Value {
then2Fulfilled = true
return nil
},
func(_ *v8go.FunctionCallbackInfo) *v8go.Value {
then2Rejected = true
return nil
},
)
ctx.PerformMicrotaskCheckpoint()
if thenInfo == nil {
t.Fatalf("expected Then to be called on already-resolved promise, but was not")
}
if len(thenInfo.Args()) != 1 || thenInfo.Args()[0].String() != val2.String() {
t.Fatalf("expected [%v], was: %+v", val2, thenInfo.Args())
}

if then2Fulfilled {
t.Fatalf("unexpectedly called onFulfilled")
}
if !then2Rejected {
t.Fatalf("expected call to onRejected, got none")
}
}
53 changes: 53 additions & 0 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ IsolatePtr NewIsolate() {
return static_cast<IsolatePtr>(iso);
}

void IsolatePerformMicrotaskCheckpoint(IsolatePtr ptr) {
ISOLATE_SCOPE(ptr)
iso->PerformMicrotaskCheckpoint();
}

void IsolateDispose(IsolatePtr ptr) {
if (ptr == nullptr) {
return;
Expand Down Expand Up @@ -1077,6 +1082,54 @@ int PromiseState(ValuePtr ptr) {
return promise->State();
}

ValuePtr PromiseThen(ValuePtr ptr, int callback_ref) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
Local<Integer> cbData = Integer::New(iso, callback_ref);
Local<Function> func = Function::New(local_ctx, FunctionTemplateCallback, cbData)
.ToLocalChecked();
Local<Promise> result = promise->Then(local_ctx, func).ToLocalChecked();
m_value* promise_val = new m_value;
promise_val->iso = iso;
promise_val->ctx = ctx;
promise_val->ptr =
Persistent<Value, CopyablePersistentTraits<Value>>(iso, promise);
return tracked_value(ctx, promise_val);
}

ValuePtr PromiseThen2(ValuePtr ptr, int on_fulfilled_ref, int on_rejected_ref) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
Local<Integer> onFulfilledData = Integer::New(iso, on_fulfilled_ref);
Local<Function> onFulfilledFunc = Function::New(local_ctx, FunctionTemplateCallback, onFulfilledData)
.ToLocalChecked();
Local<Integer> onRejectedData = Integer::New(iso, on_rejected_ref);
Local<Function> onRejectedFunc = Function::New(local_ctx, FunctionTemplateCallback, onRejectedData)
.ToLocalChecked();
Local<Promise> result = promise->Then(local_ctx, onFulfilledFunc, onRejectedFunc).ToLocalChecked();
m_value* promise_val = new m_value;
promise_val->iso = iso;
promise_val->ctx = ctx;
promise_val->ptr =
Persistent<Value, CopyablePersistentTraits<Value>>(iso, promise);
return tracked_value(ctx, promise_val);
}

ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
Local<Integer> cbData = Integer::New(iso, callback_ref);
Local<Function> func = Function::New(local_ctx, FunctionTemplateCallback, cbData)
.ToLocalChecked();
Local<Promise> result = promise->Catch(local_ctx, func).ToLocalChecked();
m_value* promise_val = new m_value;
promise_val->iso = iso;
promise_val->ctx = ctx;
promise_val->ptr =
Persistent<Value, CopyablePersistentTraits<Value>>(iso, promise);
return tracked_value(ctx, promise_val);
}

ValuePtr PromiseResult(ValuePtr ptr) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
Expand Down
4 changes: 4 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ typedef struct {

extern void Init();
extern IsolatePtr NewIsolate();
extern void IsolatePerformMicrotaskCheckpoint(IsolatePtr ptr);
extern void IsolateDispose(IsolatePtr ptr);
extern void IsolateTerminateExecution(IsolatePtr ptr);
extern IsolateHStatistics IsolationGetHeapStatistics(IsolatePtr ptr);
Expand Down Expand Up @@ -171,6 +172,9 @@ extern ValuePtr PromiseResolverGetPromise(ValuePtr ptr);
int PromiseResolverResolve(ValuePtr ptr, ValuePtr val_ptr);
int PromiseResolverReject(ValuePtr ptr, ValuePtr val_ptr);
int PromiseState(ValuePtr ptr);
ValuePtr PromiseThen(ValuePtr ptr, int callback_ref);
ValuePtr PromiseThen2(ValuePtr ptr, int on_fulfilled_ref, int on_rejected_ref);
ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref);
extern ValuePtr PromiseResult(ValuePtr ptr);

extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]);
Expand Down

0 comments on commit 36874e7

Please sign in to comment.