diff --git a/crates/neon/src/context/internal.rs b/crates/neon/src/context/internal.rs index 1f8f55cfc..52164a5d6 100644 --- a/crates/neon/src/context/internal.rs +++ b/crates/neon/src/context/internal.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, ffi::c_void, mem::MaybeUninit}; use crate::{ - context::ModuleContext, + context::{Cx, ModuleContext}, handle::Handle, result::NeonResult, sys::{self, raw}, @@ -46,8 +46,12 @@ impl Env { } } -pub trait ContextInternal<'a>: Sized { - fn env(&self) -> Env; +pub trait ContextInternal<'cx>: Sized { + fn cx(&self) -> &Cx<'cx>; + fn cx_mut(&mut self) -> &mut Cx<'cx>; + fn env(&self) -> Env { + self.cx().env + } } fn default_main(mut cx: ModuleContext) -> NeonResult<()> { diff --git a/crates/neon/src/context/mod.rs b/crates/neon/src/context/mod.rs index 0d936ed3f..7bad93f4e 100644 --- a/crates/neon/src/context/mod.rs +++ b/crates/neon/src/context/mod.rs @@ -34,6 +34,31 @@ //! } //! ``` //! +//! ## Writing Generic Helpers +//! +//! Depending on the entrypoint, a user may have a [`FunctionContext`], [`ModuleContext`], or +//! generic [`Cx`]. While it is possible to write a helper that is generic over the [`Context`] +//! trait, it is often simpler to accept a [`Cx`] argument. Due to deref coercion, other contexts +//! may be passed into a function that accepts a reference to [`Cx`]. +//! +//! ``` +//! # use neon::prelude::*; +//! fn log(cx: &mut Cx, msg: &str) -> NeonResult<()> { +//! cx.global::("console")? +//! .call_method_with(cx, "log")? +//! .arg(cx.string(msg)) +//! .exec(cx)?; +//! +//! Ok(()) +//! } +//! +//! fn print(mut cx: FunctionContext) -> JsResult { +//! let msg = cx.argument::(0)?.value(&mut cx); +//! log(&mut cx, &msg)?; +//! Ok(cx.undefined()) +//! } +//! ``` +//! //! ## Memory Management //! //! Because contexts represent the engine at a point in time, they are associated with a @@ -141,7 +166,12 @@ pub(crate) mod internal; -use std::{convert::Into, marker::PhantomData, panic::UnwindSafe}; +use std::{ + convert::Into, + marker::PhantomData, + ops::{Deref, DerefMut}, + panic::UnwindSafe, +}; pub use crate::types::buffer::lock::Lock; @@ -175,10 +205,94 @@ use crate::types::date::{DateError, JsDate}; #[cfg(feature = "napi-6")] use crate::lifecycle::InstanceData; +#[doc(hidden)] +/// An execution context of a task completion callback. +pub type TaskContext<'cx> = Cx<'cx>; + +#[doc(hidden)] +/// An execution context of a scope created by [`Context::execute_scoped()`](Context::execute_scoped). +pub type ExecuteContext<'cx> = Cx<'cx>; + +#[doc(hidden)] +/// An execution context of a scope created by [`Context::compute_scoped()`](Context::compute_scoped). +pub type ComputeContext<'cx> = Cx<'cx>; + +#[doc(hidden)] +/// A view of the JS engine in the context of a finalize method on garbage collection +pub type FinalizeContext<'cx> = Cx<'cx>; + +/// An execution context constructed from a raw [`Env`](crate::sys::bindings::Env). +#[cfg(feature = "sys")] +#[cfg_attr(docsrs, doc(cfg(feature = "sys")))] +#[doc(hidden)] +pub type SysContext<'cx> = Cx<'cx>; + +/// Context representing access to the JavaScript runtime +pub struct Cx<'cx> { + env: Env, + _phantom_inner: PhantomData<&'cx ()>, +} + +impl<'cx> Cx<'cx> { + /// Creates a context from a raw `Env`. + /// + /// # Safety + /// + /// Once a [`Cx`] has been created, it is unsafe to use + /// the `Env`. The handle scope for the `Env` must be valid for + /// the lifetime `'cx`. + #[cfg(feature = "sys")] + #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] + pub unsafe fn from_raw(env: sys::Env) -> Self { + Self { + env: env.into(), + _phantom_inner: PhantomData, + } + } + + fn new(env: Env) -> Self { + Self { + env, + _phantom_inner: PhantomData, + } + } + + pub(crate) fn with_context FnOnce(Cx<'b>) -> T>(env: Env, f: F) -> T { + f(Self { + env, + _phantom_inner: PhantomData, + }) + } +} + +impl<'cx> ContextInternal<'cx> for Cx<'cx> { + fn cx(&self) -> &Cx<'cx> { + self + } + + fn cx_mut(&mut self) -> &mut Cx<'cx> { + self + } +} + +impl<'cx> Context<'cx> for Cx<'cx> {} + +impl<'cx> From> for Cx<'cx> { + fn from(cx: FunctionContext<'cx>) -> Self { + cx.cx + } +} + +impl<'cx> From> for Cx<'cx> { + fn from(cx: ModuleContext<'cx>) -> Self { + cx.cx + } +} + #[repr(C)] -pub(crate) struct CallbackInfo<'a> { +pub(crate) struct CallbackInfo<'cx> { info: raw::FunctionCallbackInfo, - _lifetime: PhantomData<&'a raw::FunctionCallbackInfo>, + _lifetime: PhantomData<&'cx raw::FunctionCallbackInfo>, } impl CallbackInfo<'_> { @@ -276,14 +390,11 @@ pub trait Context<'a>: ContextInternal<'a> { fn execute_scoped<'b, T, F>(&mut self, f: F) -> T where 'a: 'b, - F: FnOnce(ExecuteContext<'b>) -> T, + F: FnOnce(Cx<'b>) -> T, { let env = self.env(); let scope = unsafe { HandleScope::new(env.to_raw()) }; - let result = f(ExecuteContext { - env, - _phantom_inner: PhantomData, - }); + let result = f(Cx::new(env)); drop(scope); @@ -299,14 +410,11 @@ pub trait Context<'a>: ContextInternal<'a> { where 'a: 'b, V: Value, - F: FnOnce(ComputeContext<'b>) -> JsResult<'b, V>, + F: FnOnce(Cx<'b>) -> JsResult<'b, V>, { let env = self.env(); let scope = unsafe { EscapableHandleScope::new(env.to_raw()) }; - let cx = ComputeContext { - env, - phantom_inner: PhantomData, - }; + let cx = Cx::new(env); let escapee = unsafe { scope.escape(f(cx)?.to_local()) }; @@ -551,20 +659,37 @@ pub trait Context<'a>: ContextInternal<'a> { } /// An execution context of module initialization. -pub struct ModuleContext<'a> { - env: Env, - exports: Handle<'a, JsObject>, +pub struct ModuleContext<'cx> { + cx: Cx<'cx>, + exports: Handle<'cx, JsObject>, +} + +impl<'cx> Deref for ModuleContext<'cx> { + type Target = Cx<'cx>; + + fn deref(&self) -> &Self::Target { + self.cx() + } } -impl<'a> UnwindSafe for ModuleContext<'a> {} +impl<'cx> DerefMut for ModuleContext<'cx> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.cx_mut() + } +} -impl<'a> ModuleContext<'a> { +impl<'cx> UnwindSafe for ModuleContext<'cx> {} + +impl<'cx> ModuleContext<'cx> { pub(crate) fn with FnOnce(ModuleContext<'b>) -> T>( env: Env, - exports: Handle<'a, JsObject>, + exports: Handle<'cx, JsObject>, f: F, ) -> T { - f(ModuleContext { env, exports }) + f(ModuleContext { + cx: Cx::new(env), + exports, + }) } #[cfg(not(feature = "napi-5"))] @@ -600,60 +725,50 @@ impl<'a> ModuleContext<'a> { } /// Produces a handle to a module's exports object. - pub fn exports_object(&mut self) -> JsResult<'a, JsObject> { + pub fn exports_object(&mut self) -> JsResult<'cx, JsObject> { Ok(self.exports) } } -impl<'a> ContextInternal<'a> for ModuleContext<'a> { - fn env(&self) -> Env { - self.env +impl<'cx> ContextInternal<'cx> for ModuleContext<'cx> { + fn cx(&self) -> &Cx<'cx> { + &self.cx } -} - -impl<'a> Context<'a> for ModuleContext<'a> {} - -/// An execution context of a scope created by [`Context::execute_scoped()`](Context::execute_scoped). -pub struct ExecuteContext<'a> { - env: Env, - _phantom_inner: PhantomData<&'a ()>, -} -impl<'a> ContextInternal<'a> for ExecuteContext<'a> { - fn env(&self) -> Env { - self.env + fn cx_mut(&mut self) -> &mut Cx<'cx> { + &mut self.cx } } -impl<'a> Context<'a> for ExecuteContext<'a> {} - -/// An execution context of a scope created by [`Context::compute_scoped()`](Context::compute_scoped). -pub struct ComputeContext<'a> { - env: Env, - phantom_inner: PhantomData<&'a ()>, -} - -impl<'a> ContextInternal<'a> for ComputeContext<'a> { - fn env(&self) -> Env { - self.env - } -} - -impl<'a> Context<'a> for ComputeContext<'a> {} +impl<'cx> Context<'cx> for ModuleContext<'cx> {} /// An execution context of a function call. /// /// The type parameter `T` is the type of the `this`-binding. -pub struct FunctionContext<'a> { - env: Env, - info: &'a CallbackInfo<'a>, +pub struct FunctionContext<'cx> { + cx: Cx<'cx>, + info: &'cx CallbackInfo<'cx>, arguments: Option, } -impl<'a> UnwindSafe for FunctionContext<'a> {} +impl<'cx> Deref for FunctionContext<'cx> { + type Target = Cx<'cx>; + + fn deref(&self) -> &Self::Target { + &self.cx + } +} + +impl<'cx> DerefMut for FunctionContext<'cx> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.cx + } +} -impl<'a> FunctionContext<'a> { +impl<'cx> UnwindSafe for FunctionContext<'cx> {} + +impl<'cx> FunctionContext<'cx> { /// Indicates whether the function was called with `new`. pub fn kind(&self) -> CallKind { self.info.kind(self) @@ -661,11 +776,11 @@ impl<'a> FunctionContext<'a> { pub(crate) fn with FnOnce(FunctionContext<'b>) -> U>( env: Env, - info: &'a CallbackInfo<'a>, + info: &'cx CallbackInfo<'cx>, f: F, ) -> U { f(FunctionContext { - env, + cx: Cx::new(env), info, arguments: None, }) @@ -682,7 +797,7 @@ impl<'a> FunctionContext<'a> { } /// Produces the `i`th argument, or `None` if `i` is greater than or equal to `self.len()`. - pub fn argument_opt(&mut self, i: usize) -> Option> { + pub fn argument_opt(&mut self, i: usize) -> Option> { let argv = if let Some(argv) = self.arguments.as_ref() { argv } else { @@ -695,7 +810,7 @@ impl<'a> FunctionContext<'a> { } /// Produces the `i`th argument and casts it to the type `V`, or throws an exception if `i` is greater than or equal to `self.len()` or cannot be cast to `V`. - pub fn argument(&mut self, i: usize) -> JsResult<'a, V> { + pub fn argument(&mut self, i: usize) -> JsResult<'cx, V> { match self.argument_opt(i) { Some(v) => v.downcast_or_throw(self), None => self.throw_type_error("not enough arguments"), @@ -706,12 +821,12 @@ impl<'a> FunctionContext<'a> { /// Equivalent to calling `cx.this_value().downcast_or_throw(&mut cx)`. /// /// Throws an exception if the value is a different type. - pub fn this(&mut self) -> JsResult<'a, T> { + pub fn this(&mut self) -> JsResult<'cx, T> { self.this_value().downcast_or_throw(self) } /// Produces a handle to the function's [`this`-binding](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#function_context). - pub fn this_value(&mut self) -> Handle<'a, JsValue> { + pub fn this_value(&mut self) -> Handle<'cx, JsValue> { JsValue::new_internal(self.info.this(self)) } @@ -731,7 +846,7 @@ impl<'a> FunctionContext<'a> { /// ``` pub fn args(&mut self) -> NeonResult where - T: FromArgs<'a>, + T: FromArgs<'cx>, { T::from_args(self) } @@ -755,104 +870,24 @@ impl<'a> FunctionContext<'a> { /// ``` pub fn args_opt(&mut self) -> NeonResult> where - T: FromArgs<'a>, + T: FromArgs<'cx>, { T::from_args_opt(self) } - pub(crate) fn argv(&mut self) -> [Handle<'a, JsValue>; N] { + pub(crate) fn argv(&mut self) -> [Handle<'cx, JsValue>; N] { self.info.argv_exact(self) } } -impl<'a> ContextInternal<'a> for FunctionContext<'a> { - fn env(&self) -> Env { - self.env - } -} - -impl<'a> Context<'a> for FunctionContext<'a> {} - -/// An execution context of a task completion callback. -pub struct TaskContext<'a> { - env: Env, - _phantom_inner: PhantomData<&'a ()>, -} - -impl<'a> TaskContext<'a> { - pub(crate) fn with_context FnOnce(TaskContext<'b>) -> T>(env: Env, f: F) -> T { - f(Self { - env, - _phantom_inner: PhantomData, - }) +impl<'cx> ContextInternal<'cx> for FunctionContext<'cx> { + fn cx(&self) -> &Cx<'cx> { + &self.cx } -} -impl<'a> ContextInternal<'a> for TaskContext<'a> { - fn env(&self) -> Env { - self.env + fn cx_mut(&mut self) -> &mut Cx<'cx> { + &mut self.cx } } -impl<'a> Context<'a> for TaskContext<'a> {} - -/// A view of the JS engine in the context of a finalize method on garbage collection -pub(crate) struct FinalizeContext<'a> { - env: Env, - _phantom_inner: PhantomData<&'a ()>, -} - -impl<'a> FinalizeContext<'a> { - pub(crate) fn with FnOnce(FinalizeContext<'b>) -> T>(env: Env, f: F) -> T { - f(Self { - env, - _phantom_inner: PhantomData, - }) - } -} - -impl<'a> ContextInternal<'a> for FinalizeContext<'a> { - fn env(&self) -> Env { - self.env - } -} - -impl<'a> Context<'a> for FinalizeContext<'a> {} - -#[cfg(feature = "sys")] -#[cfg_attr(docsrs, doc(cfg(feature = "sys")))] -/// An execution context constructed from a raw [`Env`](crate::sys::bindings::Env). -pub struct SysContext<'cx> { - env: Env, - _phantom_inner: PhantomData<&'cx ()>, -} - -#[cfg(feature = "sys")] -impl<'cx> SysContext<'cx> { - /// Creates a context from a raw `Env`. - /// - /// # Safety - /// - /// Once a `SysContext` has been created, it is unsafe to use - /// the `Env`. The handle scope for the `Env` must be valid for - /// the lifetime `'cx`. - pub unsafe fn from_raw(env: sys::Env) -> Self { - Self { - env: env.into(), - _phantom_inner: PhantomData, - } - } -} - -#[cfg(feature = "sys")] -impl<'cx> SysContext<'cx> {} - -#[cfg(feature = "sys")] -impl<'cx> ContextInternal<'cx> for SysContext<'cx> { - fn env(&self) -> Env { - self.env - } -} - -#[cfg(feature = "sys")] -impl<'cx> Context<'cx> for SysContext<'cx> {} +impl<'cx> Context<'cx> for FunctionContext<'cx> {} diff --git a/crates/neon/src/event/channel.rs b/crates/neon/src/event/channel.rs index a665e0a82..860292bc6 100644 --- a/crates/neon/src/event/channel.rs +++ b/crates/neon/src/event/channel.rs @@ -7,7 +7,7 @@ use std::{ }; use crate::{ - context::{Context, TaskContext}, + context::{Context, Cx}, result::{NeonResult, ResultExt, Throw}, sys::{raw::Env, tsfn::ThreadsafeFunction}, }; @@ -143,7 +143,7 @@ impl Channel { pub fn send(&self, f: F) -> JoinHandle where T: Send + 'static, - F: FnOnce(TaskContext) -> NeonResult + Send + 'static, + F: FnOnce(Cx) -> NeonResult + Send + 'static, { self.try_send(f).unwrap() } @@ -155,15 +155,15 @@ impl Channel { pub fn try_send(&self, f: F) -> Result, SendError> where T: Send + 'static, - F: FnOnce(TaskContext) -> NeonResult + Send + 'static, + F: FnOnce(Cx) -> NeonResult + Send + 'static, { let (tx, rx) = oneshot::channel(); let callback = Box::new(move |env| { let env = unsafe { mem::transmute(env) }; - // Note: It is sufficient to use `TaskContext`'s `InheritedHandleScope` because + // Note: It is sufficient to use `Cx` because // N-API creates a `HandleScope` before calling the callback. - TaskContext::with_context(env, move |cx| { + Cx::with_context(env, move |cx| { // Error can be ignored; it only means the user didn't join let _ = tx.send(f(cx).map_err(Into::into)); }); diff --git a/crates/neon/src/event/task.rs b/crates/neon/src/event/task.rs index 925bacd53..bfbf9b669 100644 --- a/crates/neon/src/event/task.rs +++ b/crates/neon/src/event/task.rs @@ -1,7 +1,7 @@ use std::{panic::resume_unwind, thread}; use crate::{ - context::{internal::Env, Context, TaskContext}, + context::{internal::Env, Context, Cx}, handle::Handle, result::{JsResult, NeonResult}, sys::{async_work, raw}, @@ -44,7 +44,7 @@ where /// of the `execute` callback pub fn and_then(self, complete: F) where - F: FnOnce(TaskContext, O) -> NeonResult<()> + 'static, + F: FnOnce(Cx, O) -> NeonResult<()> + 'static, { let env = self.cx.env(); let execute = self.execute; @@ -61,7 +61,7 @@ where pub fn promise(self, complete: F) -> Handle<'a, JsPromise> where V: Value, - F: FnOnce(TaskContext, O) -> JsResult + 'static, + F: FnOnce(Cx, O) -> JsResult + 'static, { let env = self.cx.env(); let (deferred, promise) = JsPromise::new(self.cx); @@ -78,7 +78,7 @@ fn schedule(env: Env, input: I, data: D) where I: FnOnce() -> O + Send + 'static, O: Send + 'static, - D: FnOnce(TaskContext, O) -> NeonResult<()> + 'static, + D: FnOnce(Cx, O) -> NeonResult<()> + 'static, { unsafe { async_work::schedule(env.to_raw(), input, execute::, complete::, data); @@ -96,7 +96,7 @@ where fn complete(env: raw::Env, output: thread::Result, callback: D) where O: Send + 'static, - D: FnOnce(TaskContext, O) -> NeonResult<()> + 'static, + D: FnOnce(Cx, O) -> NeonResult<()> + 'static, { let output = output.unwrap_or_else(|panic| { // If a panic was caught while executing the task on the Node Worker @@ -104,7 +104,7 @@ where resume_unwind(panic) }); - TaskContext::with_context(env.into(), move |cx| { + Cx::with_context(env.into(), move |cx| { let _ = callback(cx, output); }); } @@ -114,7 +114,7 @@ fn schedule_promise(env: Env, input: I, complete: D, deferred: Defer where I: FnOnce() -> O + Send + 'static, O: Send + 'static, - D: FnOnce(TaskContext, O) -> JsResult + 'static, + D: FnOnce(Cx, O) -> JsResult + 'static, V: Value, { unsafe { @@ -134,12 +134,12 @@ fn complete_promise( (complete, deferred): (D, Deferred), ) where O: Send + 'static, - D: FnOnce(TaskContext, O) -> JsResult + 'static, + D: FnOnce(Cx, O) -> JsResult + 'static, V: Value, { let env = env.into(); - TaskContext::with_context(env, move |cx| { + Cx::with_context(env, move |cx| { deferred.try_catch_settle(cx, move |cx| { let output = output.unwrap_or_else(|panic| resume_unwind(panic)); diff --git a/crates/neon/src/prelude.rs b/crates/neon/src/prelude.rs index 01b87f55e..055ae1f9d 100644 --- a/crates/neon/src/prelude.rs +++ b/crates/neon/src/prelude.rs @@ -2,10 +2,7 @@ #[doc(no_inline)] pub use crate::{ - context::{ - CallKind, ComputeContext, Context, ExecuteContext, FunctionContext, ModuleContext, - TaskContext, - }, + context::{CallKind, Context, Cx, FunctionContext, ModuleContext}, handle::{Handle, Root}, object::Object, result::{JsResult, NeonResult, ResultExt as NeonResultExt}, @@ -18,6 +15,9 @@ pub use crate::{ }, }; +#[doc(hidden)] +pub use crate::context::{ComputeContext, ExecuteContext, TaskContext}; + #[cfg(feature = "napi-4")] #[doc(no_inline)] pub use crate::event::{Channel, SendError}; diff --git a/crates/neon/src/sys/mod.rs b/crates/neon/src/sys/mod.rs index 10bdde4fc..391ce5a91 100644 --- a/crates/neon/src/sys/mod.rs +++ b/crates/neon/src/sys/mod.rs @@ -53,12 +53,12 @@ //! # #[cfg(feature = "sys")] //! # { //! # let env = std::ptr::null_mut().cast(); -//! use neon::{context::SysContext, prelude::*, sys::bindings}; +//! use neon::{context::Cx, prelude::*, sys::bindings}; //! //! let cx = unsafe { //! neon::sys::setup(env); //! -//! SysContext::from_raw(env) +//! Cx::from_raw(env) //! }; //! //! let raw_string: bindings::Value = cx.string("Hello, World!").to_raw(); diff --git a/crates/neon/src/types_impl/boxed.rs b/crates/neon/src/types_impl/boxed.rs index e54330757..1caf8ae87 100644 --- a/crates/neon/src/types_impl/boxed.rs +++ b/crates/neon/src/types_impl/boxed.rs @@ -4,7 +4,7 @@ use std::{ }; use crate::{ - context::{internal::Env, Context, FinalizeContext}, + context::{internal::Env, Context, Cx}, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, sys::{external, raw}, @@ -244,7 +244,7 @@ impl JsBox { let data = *data.downcast::().unwrap(); let env = unsafe { std::mem::transmute(env) }; - FinalizeContext::with(env, move |mut cx| data.finalize(&mut cx)); + Cx::with_context(env, move |mut cx| data.finalize(&mut cx)); } let v = Box::new(value) as BoxAny; diff --git a/crates/neon/src/types_impl/promise.rs b/crates/neon/src/types_impl/promise.rs index 913d788ab..0f817854a 100644 --- a/crates/neon/src/types_impl/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -11,7 +11,7 @@ use crate::{ #[cfg(feature = "napi-4")] use crate::{ - context::TaskContext, + context::Cx, event::{Channel, JoinHandle, SendError}, }; @@ -177,9 +177,7 @@ impl JsPromise { where O: Send + 'static, C: Context<'a>, - F: FnOnce(TaskContext, Result, Handle>) -> NeonResult - + Send - + 'static, + F: FnOnce(Cx, Result, Handle>) -> NeonResult + Send + 'static, { let then = self.get::(cx, "then")?; @@ -207,7 +205,7 @@ impl JsPromise { let (f, tx) = take_state(); let v = cx.argument::(0)?; - TaskContext::with_context(cx.env(), move |cx| { + Cx::with_context(cx.env(), move |cx| { // Error indicates that the `Future` has already dropped; ignore let _ = tx.send(f(cx, Ok(v)).map_err(Into::into)); }); @@ -221,7 +219,7 @@ impl JsPromise { let (f, tx) = take_state(); let v = cx.argument::(0)?; - TaskContext::with_context(cx.env(), move |cx| { + Cx::with_context(cx.env(), move |cx| { // Error indicates that the `Future` has already dropped; ignore let _ = tx.send(f(cx, Err(v)).map_err(Into::into)); }); @@ -327,7 +325,7 @@ impl Deferred { ) -> Result, SendError> where V: Value, - F: FnOnce(TaskContext) -> JsResult + Send + 'static, + F: FnOnce(Cx) -> JsResult + Send + 'static, { channel.try_send(move |cx| { self.try_catch_settle(cx, complete); @@ -356,7 +354,7 @@ impl Deferred { pub fn settle_with(self, channel: &Channel, complete: F) -> JoinHandle<()> where V: Value, - F: FnOnce(TaskContext) -> JsResult + Send + 'static, + F: FnOnce(Cx) -> JsResult + Send + 'static, { self.try_settle_with(channel, complete).unwrap() }