Skip to content

Commit

Permalink
Promise resolver and promise result (#76)
Browse files Browse the repository at this point in the history
* Promise resolver and promise result

* update persistent value to match master

* Add docs, test and Exception types

* remove the exception error functions

* add test for when value is not a promise

* update changelog
  • Loading branch information
rogchap authored Mar 7, 2021
1 parent 9848a00 commit 794781e
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Promise resolver and promise result

## [v0.5.1] - 2021-02-19

### Fixed
Expand Down
24 changes: 19 additions & 5 deletions function_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,32 @@ func ExampleFunctionTemplate() {
func ExampleFunctionTemplate_fetch() {
iso, _ := v8go.NewIsolate()
global, _ := v8go.NewObjectTemplate(iso)

fetchfn, _ := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
args := info.Args()
url := args[0].String()
res, _ := http.Get(url)
body, _ := ioutil.ReadAll(res.Body)
val, _ := v8go.NewValue(iso, string(body))
return val

resolver, _ := v8go.NewPromiseResolver(info.Context())

go func() {
res, _ := http.Get(url)
body, _ := ioutil.ReadAll(res.Body)
val, _ := v8go.NewValue(iso, string(body))
resolver.Resolve(val)
}()
return resolver.GetPromise().Value
})
global.Set("fetch", fetchfn, v8go.ReadOnly)

ctx, _ := v8go.NewContext(iso, global)
val, _ := ctx.RunScript("fetch('https://rogchap.com/v8go')", "")
fmt.Printf("%s\n", strings.Split(val.String(), "\n")[0])
prom, _ := val.AsPromise()

// wait for the promise to resolve
for prom.State() == v8go.Pending {
continue
}
fmt.Printf("%s\n", strings.Split(prom.Result().String(), "\n")[0])
// Output:
// <!DOCTYPE html>
}
87 changes: 87 additions & 0 deletions promise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package v8go

// #include <stdlib.h>
// #include "v8go.h"
import "C"
import (
"errors"
)

// PromiseState is the state of the Promise.
type PromiseState int

const (
Pending PromiseState = iota
Fulfilled
Rejected
)

// PromiseResolver is the resolver object for the promise.
// Most cases will create a new PromiseResolver and return
// the associated Promise from the resolver.
type PromiseResolver struct {
*Object
prom *Promise
}

// Promise is the JavaScript promise object defined in ES6
type Promise struct {
*Object
}

// MewPromiseResolver creates a new Promise resolver for the given context.
// The associated Promise will be in a Pending state.
func NewPromiseResolver(ctx *Context) (*PromiseResolver, error) {
if ctx == nil {
return nil, errors.New("v8go: Context is required")
}
ptr := C.NewPromiseResolver(ctx.ptr)
val := &Value{ptr, ctx}
return &PromiseResolver{&Object{val}, nil}, nil
}

// GetPromise returns the associated Promise object for this resolver.
// The Promise object is unique to the resolver and returns the same object
// on multiple calls.
func (r *PromiseResolver) GetPromise() *Promise {
if r.prom == nil {
ptr := C.PromiseResolverGetPromise(r.ptr)
val := &Value{ptr, r.ctx}
r.prom = &Promise{&Object{val}}
}
return r.prom
}

// Resolve invokes the Promise resolve state with the given value.
// The Promise state will transition from Pending to Fulfilled.
func (r *PromiseResolver) Resolve(val Valuer) bool {
r.ctx.register()
defer r.ctx.deregister()
return C.PromiseResolverResolve(r.ptr, val.value().ptr) != 0
}

// Reject invokes the Promise reject state with the given value.
// The Promise state will transition from Pending to Rejected.
func (r *PromiseResolver) Reject(err *Value) bool {
r.ctx.register()
defer r.ctx.deregister()
return C.PromiseResolverReject(r.ptr, err.ptr) != 0
}

// State returns the current state of the Promise.
func (p *Promise) State() PromiseState {
return PromiseState(C.PromiseState(p.ptr))
}

// Result is the value result of the Promise. The Promise must
// NOT be in a Pending state, otherwise may panic. Call promise.State()
// to validate state before calling for the result.
func (p *Promise) Result() *Value {
ptr := C.PromiseResult(p.ptr)
val := &Value{ptr, p.ctx}
return val
}
47 changes: 47 additions & 0 deletions promise_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package v8go_test

import (
"testing"

"rogchap.com/v8go"
)

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

iso, _ := v8go.NewIsolate()
ctx, _ := v8go.NewContext(iso)
if _, err := v8go.NewPromiseResolver(nil); err == nil {
t.Error("expected error with <nil> Context")
}

res1, _ := v8go.NewPromiseResolver(ctx)
prom1 := res1.GetPromise()
if s := prom1.State(); s != v8go.Pending {
t.Errorf("unexpected state for Promise, want Pending (0) got: %v", s)
}

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

if s := prom1.State(); s != v8go.Fulfilled {
t.Fatalf("unexpected state for Promise, want Fulfilled (1) got: %v", s)
}

if result := prom1.Result(); result.String() != val1.String() {
t.Errorf("expected the Promise result to match the resolve value, but got: %s", result)
}

res2, _ := v8go.NewPromiseResolver(ctx)
val2, _ := v8go.NewValue(iso, "Bad Foo")
res2.Reject(val2)

prom2 := res2.GetPromise()
if s := prom2.State(); s != v8go.Rejected {
t.Fatalf("unexpected state for Promise, want Rejected (2) got: %v", s)
}
}
118 changes: 117 additions & 1 deletion v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ ValuePtr NewValueInteger(IsolatePtr iso_ptr, int32_t v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, Integer::New(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -515,6 +516,7 @@ ValuePtr NewValueIntegerFromUnsigned(IsolatePtr iso_ptr, uint32_t v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, Integer::NewFromUnsigned(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -524,6 +526,7 @@ ValuePtr NewValueString(IsolatePtr iso_ptr, const char* v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, String::NewFromUtf8(iso, v).ToLocalChecked());
return static_cast<ValuePtr>(val);
Expand All @@ -533,6 +536,7 @@ ValuePtr NewValueBoolean(IsolatePtr iso_ptr, int v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, Boolean::New(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -542,6 +546,7 @@ ValuePtr NewValueNumber(IsolatePtr iso_ptr, double v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, Number::New(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -551,6 +556,7 @@ ValuePtr NewValueBigInt(IsolatePtr iso_ptr, int64_t v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, BigInt::New(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -560,6 +566,7 @@ ValuePtr NewValueBigIntFromUnsigned(IsolatePtr iso_ptr, uint64_t v) {
ISOLATE_SCOPE(iso_ptr);
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
iso, BigInt::NewFromUnsigned(iso, v));
return static_cast<ValuePtr>(val);
Expand All @@ -574,6 +581,7 @@ ValuePtr NewValueBigIntFromWords(IsolatePtr iso_ptr,

m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
MaybeLocal<BigInt> bigint =
BigInt::NewFromWords(ctx->ptr.Get(iso), sign_bit, word_count, words);
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(
Expand Down Expand Up @@ -968,7 +976,7 @@ RtnValue ObjectGet(ValuePtr ptr, const char* key) {
m_value* new_val = new m_value;
new_val->iso = iso;
new_val->ctx = ctx;
new_val->ptr.Reset(iso, Persistent<Value>(iso, result.ToLocalChecked()));
new_val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, result.ToLocalChecked());

rtn.value = tracked_value(ctx, new_val);
return rtn;
Expand Down Expand Up @@ -1017,6 +1025,114 @@ int ObjectDeleteIdx(ValuePtr ptr, uint32_t idx) {
return obj->Delete(local_ctx, idx).ToChecked();
}

/********** Promise **********/

ValuePtr NewPromiseResolver(ContextPtr ctx_ptr) {
LOCAL_CONTEXT(ctx_ptr);
MaybeLocal<Promise::Resolver> resolver = Promise::Resolver::New(local_ctx);
m_value* val = new m_value;
val->iso = iso;
val->ctx = ctx;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, resolver.ToLocalChecked());
return tracked_value(ctx, val);
}

ValuePtr PromiseResolverGetPromise(ValuePtr ptr) {
LOCAL_VALUE(ptr);
Local<Promise::Resolver> resolver = value.As<Promise::Resolver>();
Local<Promise> promise = resolver->GetPromise();
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);
}

int PromiseResolverResolve(ValuePtr ptr, ValuePtr val_ptr) {
LOCAL_VALUE(ptr);
Local<Promise::Resolver> resolver = value.As<Promise::Resolver>();
m_value* resolve_val = static_cast<m_value*>(val_ptr);
return resolver->Resolve(local_ctx, resolve_val->ptr.Get(iso)).ToChecked();
}

int PromiseResolverReject(ValuePtr ptr, ValuePtr val_ptr) {
LOCAL_VALUE(ptr);
Local<Promise::Resolver> resolver = value.As<Promise::Resolver>();
m_value* reject_val = static_cast<m_value*>(val_ptr);
return resolver->Reject(local_ctx, reject_val->ptr.Get(iso)).ToChecked();
}

int PromiseState(ValuePtr ptr) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
return promise->State();
}

ValuePtr PromiseResult(ValuePtr ptr) {
LOCAL_VALUE(ptr)
Local<Promise> promise = value.As<Promise>();
Local<Value> result = promise->Result();
m_value* result_val = new m_value;
result_val->iso = iso;
result_val->ctx = ctx;
result_val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, result);
return tracked_value(ctx, result_val);
}

/******** Exceptions *********/

ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message) {
ISOLATE_SCOPE(iso_ptr);
Local<String> msg = String::NewFromUtf8(iso, message).ToLocalChecked();
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
// TODO(rogchap): This currently causes a segfault, and I'm not sure why!
// Even a simple error with an empty string causes the error: Exception::Error(String::Empty(iso))
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, Exception::Error(msg));
return static_cast<ValuePtr>(val);
}

ValuePtr ExceptionRangeError(IsolatePtr iso_ptr, const char* message) {
ISOLATE_SCOPE(iso_ptr);
Local<String> msg = String::NewFromUtf8(iso, message, NewStringType::kNormal).ToLocalChecked();
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, Exception::RangeError(msg));
return static_cast<ValuePtr>(val);
}

ValuePtr ExceptionReferenceError(IsolatePtr iso_ptr, const char* message) {
ISOLATE_SCOPE(iso_ptr);
Local<String> msg = String::NewFromUtf8(iso, message, NewStringType::kNormal).ToLocalChecked();
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, Exception::ReferenceError(msg));
return static_cast<ValuePtr>(val);
}

ValuePtr ExceptionSyntaxError(IsolatePtr iso_ptr, const char* message) {
ISOLATE_SCOPE(iso_ptr);
Local<String> msg = String::NewFromUtf8(iso, message, NewStringType::kNormal).ToLocalChecked();
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, Exception::SyntaxError(msg));
return static_cast<ValuePtr>(val);
}

ValuePtr ExceptionTypeError(IsolatePtr iso_ptr, const char* message) {
ISOLATE_SCOPE(iso_ptr);
Local<String> msg = String::NewFromUtf8(iso, message, NewStringType::kNormal).ToLocalChecked();
m_value* val = new m_value;
val->iso = iso;
val->ctx = nullptr;
val->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, Exception::TypeError(msg));
return static_cast<ValuePtr>(val);
}

/********** Version **********/

const char* Version() {
Expand Down
13 changes: 13 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,19 @@ int ObjectHasIdx(ValuePtr ptr, uint32_t idx);
int ObjectDelete(ValuePtr ptr, const char* key);
int ObjectDeleteIdx(ValuePtr ptr, uint32_t idx);

extern ValuePtr NewPromiseResolver(ContextPtr ctx_ptr);
extern ValuePtr PromiseResolverGetPromise(ValuePtr ptr);
int PromiseResolverResolve(ValuePtr ptr, ValuePtr val_ptr);
int PromiseResolverReject(ValuePtr ptr, ValuePtr val_ptr);
int PromiseState(ValuePtr ptr);
extern ValuePtr PromiseResult(ValuePtr ptr);

extern ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message);
extern ValuePtr ExceptionRangeError(IsolatePtr iso_ptr, const char* message);
extern ValuePtr ExceptionReferenceError(IsolatePtr iso_ptr, const char* message);
extern ValuePtr ExceptionSyntaxError(IsolatePtr iso_ptr, const char* message);
extern ValuePtr ExceptionTypeError(IsolatePtr iso_ptr, const char* message);

const char* Version();

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit 794781e

Please sign in to comment.