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:
parent
c1d86676c3
commit
5be0ffa08f
7 changed files with 251 additions and 74 deletions
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ mod class;
|
|||
mod context;
|
||||
mod conversion;
|
||||
pub mod module;
|
||||
mod promise;
|
||||
mod runtime;
|
||||
mod value;
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ pub use atom::{Atom, AtomRef};
|
|||
pub use class::{Class, ClassID};
|
||||
pub use context::{Context, ContextRef, EvalFlags};
|
||||
pub use conversion::*;
|
||||
pub use promise::Promise;
|
||||
pub use runtime::Runtime;
|
||||
pub use value::{Value, ValueRef, ValueType};
|
||||
|
||||
|
|
@ -49,6 +51,20 @@ pub enum Error {
|
|||
ParseError(String, String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
// Convert the error into an exception-type object which can be
|
||||
// thrown. This is *different* from try_into_value which just propagates
|
||||
// the error.
|
||||
pub fn to_js_error(&self, context: &ContextRef) -> Value {
|
||||
if let Error::Exception(e, _, _) = self {
|
||||
e.clone()
|
||||
} else {
|
||||
let message = self.to_string();
|
||||
context.new_error(&message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NulError> for Error {
|
||||
fn from(_: NulError) -> Self {
|
||||
Error::UnexpectedNul
|
||||
|
|
@ -75,35 +91,9 @@ pub(crate) fn throw_error(context: &ContextRef, error: Error) -> sys::JSValue {
|
|||
}
|
||||
|
||||
pub(crate) fn throw_string(context: &ContextRef, message: String) -> sys::JSValue {
|
||||
let ctx = context.ctx;
|
||||
match context.new_string(&message) {
|
||||
Ok(e) => unsafe {
|
||||
// Because context.new_string yields an owned Value, and will
|
||||
// clean it up on the way out, we need to explicitly DupValue a
|
||||
// reference for the `Throw` to own.
|
||||
let err = sys::JS_NewError(ctx);
|
||||
if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION {
|
||||
// GIVE UP; this is out of memory anyway things probably went
|
||||
// wrong because of that.
|
||||
return err;
|
||||
}
|
||||
|
||||
sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership.
|
||||
let prop = CString::new("message").unwrap();
|
||||
if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 {
|
||||
// Also an out of memory but we need to free the error object
|
||||
// on our way out.
|
||||
sys::JS_FreeValue(ctx, err);
|
||||
return sys::JS_MakeException(); // JS_EXCEPTION
|
||||
}
|
||||
|
||||
sys::JS_Throw(ctx, err)
|
||||
},
|
||||
Err(_) => unsafe {
|
||||
sys::JS_Throw(
|
||||
ctx,
|
||||
sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8),
|
||||
)
|
||||
},
|
||||
let err = context.new_error(&message);
|
||||
unsafe {
|
||||
sys::JS_DupValue(context.ctx, err.val);
|
||||
sys::JS_Throw(context.ctx, err.val)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
oden-js/src/promise.rs
Normal file
34
oden-js/src/promise.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::{ContextRef, Value, ValueRef};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Promise {
|
||||
pub object: Value,
|
||||
pub resolve_fn: Value,
|
||||
pub reject_fn: Value,
|
||||
}
|
||||
|
||||
impl Promise {
|
||||
pub(crate) fn new(object: Value, resolve_fn: Value, reject_fn: Value) -> Self {
|
||||
Promise {
|
||||
object,
|
||||
resolve_fn,
|
||||
reject_fn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dup(&self, ctx: &ContextRef) -> Self {
|
||||
Promise {
|
||||
object: self.object.dup(ctx),
|
||||
resolve_fn: self.resolve_fn.dup(ctx),
|
||||
reject_fn: self.reject_fn.dup(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(self, context: &ContextRef, value: &ValueRef) {
|
||||
let _ = self.resolve_fn.call(context, &[value]);
|
||||
}
|
||||
|
||||
pub fn reject(self, context: &ContextRef, value: &ValueRef) {
|
||||
let _ = self.reject_fn.call(context, &[value]);
|
||||
}
|
||||
}
|
||||
|
|
@ -310,14 +310,16 @@ impl ValueRef {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn call(&self, ctx: &ContextRef) -> Result<Value> {
|
||||
pub fn call(&self, ctx: &ContextRef, args: &[&ValueRef]) -> Result<Value> {
|
||||
// TODO: There *must* be a way to avoid this allocation.
|
||||
let mut args: Vec<sys::JSValue> = args.iter().map(|v| v.val).collect();
|
||||
unsafe {
|
||||
ctx.check_exception(sys::JS_Call(
|
||||
ctx.ctx,
|
||||
self.val,
|
||||
sys::JS_MakeUndefined(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
args.len() as i32,
|
||||
args.as_mut_ptr(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -345,9 +347,16 @@ impl Value {
|
|||
/// the runtime of the specified context, if not the context itself. This
|
||||
/// function makes no attempt to validate this.
|
||||
pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef) -> Self {
|
||||
Value::from_raw_rt(
|
||||
val,
|
||||
Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_raw_rt(val: sys::JSValue, rt: Runtime) -> Self {
|
||||
Value {
|
||||
value: ValueRef::from_raw(val),
|
||||
rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }),
|
||||
rt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -394,6 +403,15 @@ impl fmt::Debug for Value {
|
|||
}
|
||||
}
|
||||
|
||||
impl Clone for Value {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
sys::JS_DupValueRT(self.rt.rt, self.val);
|
||||
}
|
||||
Value::from_raw_rt(self.val, self.rt.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue