Starting to mess with promises

Going to want async IO, I think. And it's a fun detail that I guess
I'm in charge of deciding when to run promise completion functions. :D
This commit is contained in:
John Doty 2023-06-28 15:54:13 -07:00
parent c1d86676c3
commit 5be0ffa08f
7 changed files with 251 additions and 74 deletions

View file

@ -1,6 +1,6 @@
use crate::{
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Result,
Runtime, Value, ValueRef, ValueResult,
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise,
Result, Runtime, Value, ValueRef, ValueResult,
};
use bitflags::bitflags;
use oden_js_sys as sys;
@ -34,6 +34,7 @@ pub struct ContextRef {
pub(crate) ctx: *mut sys::JSContext,
}
// TODO: Should all these require mutability to enforce single-threadedness?
impl ContextRef {
pub(crate) fn from_raw(ctx: *mut sys::JSContext) -> Self {
ContextRef { ctx }
@ -277,6 +278,64 @@ impl ContextRef {
Value::from_raw(v, self)
}
/// Construct a new promise.
pub fn new_promise(&self) -> Result<Promise> {
unsafe {
let mut resolving_funcs: [sys::JSValue; 2] =
[sys::JS_MakeUndefined(), sys::JS_MakeUndefined()];
let val =
sys::JS_NewPromiseCapability(self.ctx, &mut resolving_funcs as *mut sys::JSValue);
if sys::JS_ValueGetTag(val) == sys::JS_TAG_EXCEPTION {
Err(self.exception_error())
} else {
Ok(Promise::new(
Value::from_raw(val, self),
Value::from_raw(resolving_funcs[0], self),
Value::from_raw(resolving_funcs[1], self),
))
}
}
}
/// Construct a new exception object, suitable for throwing.
pub fn new_error(&self, message: &str) -> Value {
let e = match self.new_string(message) {
Ok(e) => e,
Err(_) => match self.new_string("INTERNAL ERROR: Embedded NUL in message") {
Ok(e) => e,
// Faulting this hard is inexcusable.
Err(_) => return self.exception(),
},
};
unsafe {
let err = Value::from_raw(sys::JS_NewError(self.ctx), self);
// NOTE: Throughout this function we work at the lower-level
// error handling stuff because the errors are easier to
// manage. (We know how it can fail!)
if sys::JS_ValueGetTag(err.val) == sys::JS_TAG_EXCEPTION {
// GIVE UP; This is out of memory anyway things probably
// went wrong because of that. We'll return *that*
// exception.
return self.exception();
}
sys::JS_DupValue(self.ctx, e.val); // SetProperty takes ownership.
let prop = CString::new("message").unwrap();
if sys::JS_SetPropertyStr(self.ctx, err.val, prop.as_ptr(), e.val) == -1 {
// As before, we're just going to take the exception from
// the context, and drop the one we were trying to create
// on the floor.
return self.exception();
}
// We put the message in, we can return the value.
err
}
}
/// Fetch the global object for the context.
pub fn global_object(&self) -> ValueResult {
self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) })
@ -315,6 +374,23 @@ impl ContextRef {
Error::Exception(exc, desc, stack)
}
/// Process all pending async jobs. This includes all promise resolutions.
pub fn process_all_jobs(&self) -> Result<()> {
// TODO: SAFETY
// This is unsafe because multiple contexts can be sharing the same runtime and cause
// a race condition on the underlying runtime.
loop {
let mut ctx1: *mut sys::JSContext = std::ptr::null_mut();
let err = unsafe { sys::JS_ExecutePendingJob(sys::JS_GetRuntime(self.ctx), &mut ctx1) };
if err == 0 {
break;
} else if err < 0 {
return Err(ContextRef::from_raw(ctx1).exception_error());
}
}
Ok(())
}
}
#[derive(Debug)]