napi_async_local 0.2.0

Extends napi-rs with the ability to run local futures
Documentation
use std::cell::Cell;

use napi::Env;
use napi::JsFunction;
use napi::JsObject;
use napi::NapiValue;

use crate::store;

const SYM_PROMISE: &str = "Promise";
const SYM_PROMISE_EXECUTOR: &str = "napi::promise::executor";

pub fn create_promise<Res>(
  env: &Env,
  executor: Box<dyn FnOnce(Env, Box<dyn Fn(Res)>, Box<dyn Fn(napi::Error)>) -> napi::Result<()>>,
) -> napi::Result<JsObject>
where
  Res: NapiValue + 'static,
{
  let promise_key = env.create_string(SYM_PROMISE)?;
  let promise_ctor: JsFunction = env.get_global()?.get_property_unchecked(promise_key)?;

  let executor = {
    let cell = Cell::new(Some(executor));
    move |env, res, rej| {
      cell
        .take()
        .expect("This function should not be called more than once")(env, res, rej)
    }
  };

  let executor = env.create_function_from_closure(SYM_PROMISE_EXECUTOR, move |ctx| {
    let resolve_func_js: JsFunction = ctx.get(0)?;
    let reject_func: JsFunction = ctx.get(1)?;

    let resolve_func_key = store::set_store_value(ctx.env, resolve_func_js)?;
    let reject_func_key = store::set_store_value(ctx.env, reject_func)?;

    executor(
      ctx.env.to_owned(),
      Box::new({
        let env = ctx.env.clone();
        move |r| {
          let resolve_func = store::get_store_value::<JsFunction>(&env, &resolve_func_key).unwrap();
          resolve_func.call(None, &vec![r]).unwrap();
          store::delete_store_value(resolve_func_key);
          store::delete_store_value(reject_func_key);
        }
      }),
      Box::new({
        let env = ctx.env.clone();
        move |e| {
          let reject_func = store::get_store_value::<JsFunction>(&env, &reject_func_key).unwrap();
          let error = (&env).create_error(e).unwrap();
          reject_func.call(None, &vec![error]).unwrap();
          store::delete_store_value(resolve_func_key);
          store::delete_store_value(reject_func_key);
        }
      }),
    )?;

    Ok(())
  })?;

  let promise = promise_ctor.new_instance(&[executor])?;
  Ok(promise)
}