-
-
Notifications
You must be signed in to change notification settings - Fork 17
Add Isolate.SetPromiseRejectedCallback
#108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,10 +9,75 @@ package v8go | |
| import "C" | ||
|
|
||
| import ( | ||
| "runtime/cgo" | ||
| "strconv" | ||
| "sync" | ||
| "unsafe" | ||
| ) | ||
|
|
||
| // PromiseRejectEvent represents the type of event passed to | ||
| // [RejectedPromiseCallback]. The values reflect the values of | ||
| // v8::PromiseRejectEvent. | ||
| // | ||
| // See also: https://v8.github.io/api/head/classv8_1_1PromiseRejectMessage.html | ||
| type PromiseRejectEvent uint8 | ||
|
|
||
| const ( | ||
| // PromiseRejectWithNoHandler is the event that represents an unhandled | ||
| // rejection. | ||
| PromiseRejectWithNoHandler PromiseRejectEvent = 0 | ||
| // PromiseHandlerAddedAfterReject is sent when a rejection handler is added | ||
| // to a promise that has already rejected. E.g., the following code will | ||
| // result in a kPromiseRejectWithNoHandler event followed by an | ||
| // PromiseHandlerAddedAfterReject event. | ||
| // | ||
| // Promise.reject("dummy").catch(e => {}) | ||
| // | ||
| // The promise has already rejected when catch is called. | ||
| PromiseHandlerAddedAfterReject PromiseRejectEvent = 1 | ||
| // PromiseRejectAfterResolved is sent when a project is rejected after it | ||
| // has settled, e.g., the following will generate a | ||
| // PromiseRejectAfterResolved event. | ||
| // | ||
| // new Promise((resolve, reject) => { | ||
| // resolve() | ||
| // reject() | ||
| // }) | ||
| // | ||
| // If the first resolve call is replaced with a reject, a | ||
| // kPromiseRejectWithNoHandler event is sent first, followed by the | ||
| // PromiseRejectAfterResolved event. | ||
| PromiseRejectAfterResolved PromiseRejectEvent = 2 | ||
| // PromiseResolveAfterResolved is sent when a project is resolves after it | ||
| // has settled, e.g., the following will generate a | ||
| // PromiseResolveAfterResolved event. | ||
| // | ||
| // new Promise((resolve, reject) => { | ||
| // resolve() // or reject() | ||
| // resolve() | ||
| // }) | ||
| // | ||
| // If the first resolve call is replaced with a reject, a | ||
| // kPromiseRejectWithNoHandler event is sent first, followed by the | ||
| // PromiseResolveAfterResolved event. | ||
| PromiseResolveAfterResolved PromiseRejectEvent = 3 | ||
| ) | ||
|
|
||
| func (u PromiseRejectEvent) String() string { | ||
| switch u { | ||
| case PromiseRejectWithNoHandler: | ||
| return "kPromiseRejectWithNoHandler" | ||
| case PromiseHandlerAddedAfterReject: | ||
| return "kPromiseHandlerAddedAfterReject" | ||
| case PromiseRejectAfterResolved: | ||
| return "kPromiseRejectAfterResolved" | ||
| case PromiseResolveAfterResolved: | ||
| return "kPromiseResolveAfterResolved" | ||
| default: | ||
| return strconv.Itoa(int(u)) | ||
| } | ||
| } | ||
|
|
||
| // Isolate is a JavaScript VM instance with its own heap and | ||
| // garbage collector. Most applications will create one isolate | ||
| // with many V8 contexts for execution. | ||
|
|
@@ -22,6 +87,7 @@ type Isolate struct { | |
| cbMutex sync.RWMutex | ||
| cbSeq int | ||
| cbs map[int]FunctionCallbackWithError | ||
| handles []cgo.Handle | ||
|
|
||
| null *Value | ||
| undefined *Value | ||
|
|
@@ -144,6 +210,9 @@ func (i *Isolate) Dispose() { | |
| if i.ptr == nil { | ||
| return | ||
| } | ||
| for _, h := range i.handles { | ||
| h.Delete() | ||
| } | ||
| C.IsolateDispose(i.ptr) | ||
| i.ptr = nil | ||
| } | ||
|
|
@@ -184,3 +253,50 @@ func (i *Isolate) getCallback(ref int) FunctionCallbackWithError { | |
| defer i.cbMutex.RUnlock() | ||
| return i.cbs[ref] | ||
| } | ||
|
|
||
| //export goRejectedPromiseCallback | ||
| func goRejectedPromiseCallback(ctxref int, handle unsafe.Pointer, event PromiseRejectEvent, promise C.ValuePtr, value C.ValuePtr) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename |
||
| if p, err := (&Value{ptr: promise}).AsPromise(); err == nil { | ||
| msg := PromiseRejectMessage{ | ||
| Context: getContext(ctxref), | ||
| Promise: p, | ||
| Event: event, | ||
| } | ||
| if value != nil { | ||
| msg.Value = &Value{ptr: value} | ||
| } | ||
| cb := (cgo.Handle)(handle).Value().(RejectedPromiseCallback) | ||
| cb(msg) | ||
| } | ||
| } | ||
|
|
||
| // PromiseRejectMessage is passed to a [RejectedPromiseCallback] that is | ||
| // installed using [Isolate.SetPromiseRejectedCallback]. The values reflect the | ||
| // values in V8::PromiseRejectMessage | ||
| // | ||
| // See also: https://v8.github.io/api/head/classv8_1_1PromiseRejectMessage.html | ||
| type PromiseRejectMessage struct { | ||
| // Context contains the execution context where the promise was rejected | ||
| Context *Context | ||
| Promise *Promise | ||
| Event PromiseRejectEvent | ||
| // Value contains the rejected value | ||
| Value *Value | ||
| } | ||
|
|
||
| // RejectedPromiseCallback is the type for a callback clients can supply to be | ||
| // notified of rejected promises. | ||
| type RejectedPromiseCallback = func(PromiseRejectMessage) | ||
|
|
||
| func (i *Isolate) addHandle(h cgo.Handle) cgo.Handle { | ||
| i.handles = append(i.handles, h) | ||
| return h | ||
| } | ||
|
|
||
| // SetPromiseRejectedCallback installs a callback to be called when a promise is | ||
| // rejected. This includes rejections that may occur after a script value has | ||
| // been evaluated and V8 is running microtasks. | ||
| func (i *Isolate) SetPromiseRejectedCallback(cb RejectedPromiseCallback) { | ||
| handle := unsafe.Pointer(uintptr(i.addHandle(cgo.NewHandle(cb)))) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there was already a callback, this should deallocate the handle used by that callback. (which means I'm skeptical of the generic
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch - didn't think about this as I'd only set it up once. Can you elaborate on the skeptical part?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a side note, The library has (at least) two Go
In this particular case, the value is the
I might take a look at that eventually - but it's not a priority for my use cast ATM - just wanted to mention this.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think
I added
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ah, I think you're right, but maybe the type Isolate struct {
selfHandle cgo.Handle
promiseRejectedCallback RejectedPromiseCallback
}
//export goCallback
func goCallback(C.uintptr_t handle, /* ... reset */) {
iso, ok := cgo.Handle(handle).Value().(*Isolate)
iso.promiseRejectedCalback( /* ... */ );
}
func (i *Isolate) SetPromiseRejectedCallback(cb RejectedPromiseCallback) {
if i.selfHandle == 0 {
i.selfHandle = cgo.NewHandle(i)
}
// This could potentially be moved _inside_ the if statement - as we just set the
// same callback with the same handle. But could be dangerous if the handle is
// used for different purposes.
C.IsolateSetPromiseRejectedCallback(C.uintptrt(i.selfHandle), /* ... */ )
...
}
func (i *Isolate) Dispose() {
if i.selfHandle != 0 {
i.selfHandle.Delete()
}
}
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Nice 💪 I'm so glad you did :) I also experimented with Goja to have a pure Go alternative - and it uses The stalled discussion was titled, "Store error object in JSError for propagation". I would probably have suggested just to add a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A side note on the side note. In my ESM experimental branch, I have
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, just checked the readme - I see you're also to thank for Symbol support 🙌 without which I wouldn't have proper iterables in my browser
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. go vet is complaining that this is possible misuse of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - honestly, I am totally confused about the relation between C's As far as I could tell, conversion between the two should be safe, and for passing Any thoughts?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. uintptr_t is an unsigned integer type as least as large as the platform's pointers. There's also intptr_t and ptrdiff_t for relative pointers. I.e. very similar in scope of size_t and ssize_t...
Seems you shouldn't use |
||
| C.IsolateSetPromiseRejectedCallback(i.ptr, handle) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea to make a helper.
Shouldn't this be an
m_valueconstructor (withouttracked_value())? Seems RAII-worthy.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have had similar ideas, but something about cleanup I didn't think regarding freeing up of resources.
The
Contexthas a list of values to free, but that's anunordered_map<long, m_value*>- which is released inIf this had been
unordered_map<long, m_value>- that iteration shouldn't be necessary at all AFAICT - as that happens automatically indelete ctxBtw, I think that there's some mechanism in Go - which is also used some places in the code to cleanup on GC - but not all places. So a lot of temporary
Values are never freed until you delete the context. It's not a big problem for my intended use case as contexts are short-lived - but it's something I've wanted to look into.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a related note - in a different branch, I added helper functions to the struct, also to simplify commonly occurring patterns.
I'm hoping I'm not making a huge mistake - that a value could suddenly be used in a different isolate than the one that created it? If so, the iso should be an argument - and probably not present as a field in the struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And, what's with the
m_prefix? Is it a C/C++ convention?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While Value itself doesn't seem tied to an Isolate, all concrete constructors (well
New()) seem to take anIsolate. So I think we're good with that assumption.m_is used extensively in C++ standard libraries for member data, so it does feel weird to me too to have it on type names. And I can't find that it would be a libv8 thing either.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes - remember the
m_prefix from ATL I think (the MS lib to build COM objects). I adopted when I first started coding .NET 1.0 😆