Skip to content

Commit f4f7723

Browse files
author
Rob Figueiredo
committed
WIP: promise.then: run a callback on resolution of a promise
This would support ES Modules, since v8's interface returns a promise when running a module to handle top-level await and imports. The test does not currently pass: --- FAIL: TestPromiseRejected (0.01s) promise_test.go:81: expected Then to be called immediately on already-resolved promise, but was not
1 parent 571ffb0 commit f4f7723

File tree

4 files changed

+169
-1
lines changed

4 files changed

+169
-1
lines changed

promise.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,13 @@ func (p *Promise) Result() *Value {
8585
val := &Value{ptr, p.ctx}
8686
return val
8787
}
88+
89+
// Then invokes the given function when the promise has been resolved or rejected.
90+
// A Promise is returned that resolves after the given function has finished execution.
91+
func (p *Promise) Then(cb FunctionCallback) *Value {
92+
p.ctx.register()
93+
defer p.ctx.deregister()
94+
cbID := p.ctx.iso.registerCallback(cb)
95+
ptr := C.PromiseThen(p.ptr, C.int(cbID))
96+
return &Value{ptr, p.ctx}
97+
}

promise_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"rogchap.com/v8go"
1111
)
1212

13-
func TestPromise(t *testing.T) {
13+
func TestPromiseFulfilled(t *testing.T) {
1414
t.Parallel()
1515

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

28+
var thenInfo *v8go.FunctionCallbackInfo
29+
prom1thenVal := prom1.Then(func(info *v8go.FunctionCallbackInfo) *v8go.Value {
30+
thenInfo = info
31+
return nil
32+
})
33+
prom1then, _ := prom1thenVal.AsPromise()
34+
if prom1then.State() != v8go.Pending {
35+
t.Errorf("unexpected state for dependent Promise, want Pending got: %v", prom1then.State())
36+
}
37+
if thenInfo != nil {
38+
t.Error("unexpected call of Then prior to resolving the promise")
39+
}
40+
2841
val1, _ := v8go.NewValue(iso, "foo")
2942
res1.Resolve(val1)
3043

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

52+
if thenInfo == nil {
53+
t.Errorf("expected Then to be called, was not")
54+
}
55+
if len(thenInfo.Args()) != 1 || thenInfo.Args()[0].String() != "foo" {
56+
t.Errorf("expected promise to be called with [foo] args, was: %+v", thenInfo.Args())
57+
}
58+
}
59+
60+
func TestPromiseRejected(t *testing.T) {
61+
t.Parallel()
62+
63+
iso, _ := v8go.NewIsolate()
64+
ctx, _ := v8go.NewContext(iso)
65+
3966
res2, _ := v8go.NewPromiseResolver(ctx)
4067
val2, _ := v8go.NewValue(iso, "Bad Foo")
4168
res2.Reject(val2)
@@ -44,4 +71,20 @@ func TestPromise(t *testing.T) {
4471
if s := prom2.State(); s != v8go.Rejected {
4572
t.Fatalf("unexpected state for Promise, want Rejected (2) got: %v", s)
4673
}
74+
75+
var thenInfo *v8go.FunctionCallbackInfo
76+
thenVal := prom2.Then(func(info *v8go.FunctionCallbackInfo) *v8go.Value {
77+
thenInfo = info
78+
return nil
79+
})
80+
if thenInfo == nil {
81+
t.Fatalf("expected Then to be called immediately on already-resolved promise, but was not")
82+
}
83+
if len(thenInfo.Args()) != 100 {
84+
t.Fatalf("expected Then to be called with no args, was: %+v", thenInfo.Args())
85+
}
86+
_, err := thenVal.AsPromise()
87+
if err != nil {
88+
t.Fatalf("expected Then to return a promise, but: %+v", err)
89+
}
4790
}

v8go.cc

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,86 @@ RtnValue RunScript(ContextPtr ctx_ptr, const char* source, const char* origin) {
414414
return rtn;
415415
}
416416

417+
static MaybeLocal<Module> ResolveCallback(Local<Context> context,
418+
Local<String> specifier,
419+
Local<Module> referrer) {
420+
Isolate* isolate = context->GetIsolate();
421+
isolate->ThrowException(String::NewFromUtf8(isolate, "import not supported").ToLocalChecked());
422+
return MaybeLocal<Module>();
423+
}
424+
425+
RtnValue RunModule(ContextPtr ctx_ptr, const char* source, const char* origin) {
426+
LOCAL_CONTEXT(ctx_ptr);
427+
428+
Local<String> src =
429+
String::NewFromUtf8(iso, source, NewStringType::kNormal).ToLocalChecked();
430+
Local<String> ogn =
431+
String::NewFromUtf8(iso, origin, NewStringType::kNormal).ToLocalChecked();
432+
RtnValue rtn = {nullptr, nullptr};
433+
434+
ScriptOrigin script_origin(ogn, 0, 0, false, -1, Local<Value>(), false, false, true);
435+
ScriptCompiler::Source csrc(src, script_origin);
436+
MaybeLocal<Module> mmodule = ScriptCompiler::CompileModule(iso, &csrc);
437+
if (mmodule.IsEmpty()) {
438+
rtn.error = ExceptionError(try_catch, iso, local_ctx);
439+
return rtn;
440+
}
441+
442+
Local<Module> root_module = mmodule.ToLocalChecked();
443+
if (!root_module->InstantiateModule(local_ctx, &ResolveCallback).FromMaybe(false)) {
444+
rtn.error = ExceptionError(try_catch, iso, local_ctx);
445+
return rtn;
446+
}
447+
448+
Local<Value> result;
449+
if (!root_module->Evaluate(local_ctx).ToLocal(&result)) {
450+
rtn.error = ExceptionError(try_catch, iso, local_ctx);
451+
return rtn;
452+
}
453+
454+
// result is a Promise if the module has imports that need to be resolved
455+
// OR has a top-level await. imports are not supported yet, but awaits are.
456+
//
457+
// If await was not used, the result is undefined, and the module's status
458+
// should already be kEvaluated.
459+
if (result->IsPromise()) {
460+
// We have to wait. Just busy-loop and poll for completion.
461+
Local<Promise> result_promise(Local<Promise>::Cast(result));
462+
while (result_promise->State() == Promise::kPending) {
463+
iso->PerformMicrotaskCheckpoint();
464+
}
465+
if (result_promise->State() == Promise::kRejected) {
466+
if (!try_catch.HasCaught()) {
467+
iso->ThrowException(result_promise->Result());
468+
}
469+
rtn.error = ExceptionError(try_catch, iso, local_ctx);
470+
return rtn;
471+
}
472+
} else if (!result->IsUndefined()) {
473+
// Verify the invariant that result is either a Promise or undefined.
474+
RtnError err = {nullptr, nullptr, nullptr};
475+
err.msg = CopyString("expected promise or undefined");
476+
rtn.error = err;
477+
return rtn;
478+
}
479+
480+
// Verify that the module is now evaluated.
481+
if (root_module->GetStatus() != Module::kEvaluated) {
482+
RtnError err = {nullptr, nullptr, nullptr};
483+
err.msg = CopyString("module not evaluated");
484+
rtn.error = err;
485+
return rtn;
486+
}
487+
488+
m_value* val = new m_value;
489+
val->iso = iso;
490+
val->ctx = ctx;
491+
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, root_module->GetModuleNamespace());
492+
493+
rtn.value = tracked_value(ctx, val);
494+
return rtn;
495+
}
496+
417497
RtnValue JSONParse(ContextPtr ctx_ptr, const char* str) {
418498
LOCAL_CONTEXT(ctx_ptr);
419499
RtnValue rtn = {nullptr, nullptr};
@@ -1077,6 +1157,21 @@ int PromiseState(ValuePtr ptr) {
10771157
return promise->State();
10781158
}
10791159

1160+
ValuePtr PromiseThen(ValuePtr ptr, int callback_ref) {
1161+
LOCAL_VALUE(ptr)
1162+
Local<Promise> promise = value.As<Promise>();
1163+
Local<Integer> cbData = Integer::New(iso, callback_ref);
1164+
Local<Function> func = Function::New(local_ctx, FunctionTemplateCallback, cbData)
1165+
.ToLocalChecked();
1166+
Local<Promise> result = promise->Then(local_ctx, func).ToLocalChecked();
1167+
m_value* promise_val = new m_value;
1168+
promise_val->iso = iso;
1169+
promise_val->ctx = ctx;
1170+
promise_val->ptr =
1171+
Persistent<Value, CopyablePersistentTraits<Value>>(iso, promise);
1172+
return tracked_value(ctx, promise_val);
1173+
}
1174+
10801175
ValuePtr PromiseResult(ValuePtr ptr) {
10811176
LOCAL_VALUE(ptr)
10821177
Local<Promise> promise = value.As<Promise>();
@@ -1091,6 +1186,21 @@ ValuePtr PromiseResult(ValuePtr ptr) {
10911186

10921187
/********** Function **********/
10931188

1189+
ValuePtr NewFunction(ContextPtr ctx_ptr, int callback_ref) {
1190+
LOCAL_CONTEXT(ctx_ptr);
1191+
1192+
Local<Integer> cbData = Integer::New(iso, callback_ref);
1193+
Local<Function> func = Function::New(local_ctx, FunctionTemplateCallback, cbData)
1194+
.ToLocalChecked();
1195+
1196+
m_value* val = new m_value;
1197+
val->iso = iso;
1198+
val->ctx = ctx;
1199+
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, func);
1200+
1201+
return tracked_value(ctx, val);
1202+
}
1203+
10941204
RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) {
10951205
LOCAL_VALUE(ptr)
10961206
RtnValue rtn = {nullptr, nullptr};

v8go.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ extern void ContextFree(ContextPtr ptr);
6161
extern RtnValue RunScript(ContextPtr ctx_ptr,
6262
const char* source,
6363
const char* origin);
64+
extern RtnValue RunModule(ContextPtr ctx_ptr,
65+
const char* source,
66+
const char* origin);
6467
extern RtnValue JSONParse(ContextPtr ctx_ptr, const char* str);
6568
const char* JSONStringify(ContextPtr ctx_ptr, ValuePtr val_ptr);
6669
extern ValuePtr ContextGlobal(ContextPtr ctx_ptr);
@@ -171,8 +174,10 @@ extern ValuePtr PromiseResolverGetPromise(ValuePtr ptr);
171174
int PromiseResolverResolve(ValuePtr ptr, ValuePtr val_ptr);
172175
int PromiseResolverReject(ValuePtr ptr, ValuePtr val_ptr);
173176
int PromiseState(ValuePtr ptr);
177+
ValuePtr PromiseThen(ValuePtr ptr, int callback_ref);
174178
extern ValuePtr PromiseResult(ValuePtr ptr);
175179

180+
extern ValuePtr NewFunction(ContextPtr ctx_ptr, int callback_ref);
176181
extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]);
177182

178183
extern ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message);

0 commit comments

Comments
 (0)