Skip to content
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

How should I register an asynchronous method and call this asynchronous method from JS? #963

Open
hotlif opened this issue Nov 14, 2024 · 0 comments

Comments

@hotlif
Copy link

hotlif commented Nov 14, 2024

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use deno_core::anyhow::Ok;
use deno_core::v8::Global;
use deno_core::{
  serde_v8, v8, PollEventLoopOptions
};
use deno_core::{JsRuntime, RuntimeOptions};
use crate::util::error::Error;

pub struct Engine {
    v8_runtime: JsRuntime
}

impl Engine {
    pub fn new(extensions: Vec<deno_core::Extension>) -> Self {
        let v8_runtime = JsRuntime::new(RuntimeOptions {
            extensions,
            ..Default::default()
        });
        Self {
            v8_runtime: v8_runtime
        }
    }

    pub async fn execute_script(&mut self, code: String, args: serde_json::Value) -> Result<Option<serde_json::Value>, deno_core::anyhow::Error> {
        let module_specifier_url = &deno_core::ModuleSpecifier::parse("script:main.js")?;
        let mod_id = self.v8_runtime.load_side_es_module_from_code(
            module_specifier_url,
            code
        ).await?;

        let future_result = self.v8_runtime.mod_evaluate(mod_id);
        self.v8_runtime.run_event_loop(Default::default()).await?;

        let namespace = self.v8_runtime.get_module_namespace(mod_id)?;
        let scope = &mut self.v8_runtime.handle_scope();
 
        let namespace = namespace.open(scope);
        let default_name = v8::String::new(scope, "default").unwrap().into();
        let default_function = namespace.get(scope, default_name);
        let args = serde_v8::to_v8(scope, args)?;

        if default_function.is_none() {
            return Err(Error {
                message: "There is no default export function".to_string()
            }.into());
        }
        let default_function = default_function.unwrap().cast::<v8::Function>();
        let result = default_function.call(scope, default_function.into(), &[args]);
        let mut return_result = None;
        if result.is_some() && result.unwrap().is_promise() {
            let result = result.unwrap().cast::<v8::Promise>();
            let result = result.result(scope);
            let result = serde_v8::from_v8::<serde_json::Value>(scope, result)?;
 
            return_result = Some(result);
        } else if result.is_some() {
            let result = serde_v8::from_v8::<serde_json::Value>(scope, result.unwrap())?;
            return_result = Some(result);
        } else {
            return_result = None;
        }
        future_result.await?;
        Ok(return_result)
    }
}

#[cfg(test)]
mod tests {

    use super::Engine;

    #[actix_web::test]
    async fn test_asynchronous_task() -> std::io::Result<()> {
        let mut engine = Engine::new(vec![]);
        let result = engine.execute_script(
            r#"
            async function task () {
                Deno.core.print("test ---- 1 \n")
                return {
                    "name": "hello word"
                }
            }
            export default task;
            "#.to_string(),
            serde_json::json!({})
        ).await.unwrap();

        assert!(result.is_some());

        if let Some(result) = result {
            let name = result.get("name").unwrap().as_str().unwrap();
            println!("response -> {}", result.to_string());
            assert_eq!(name, "hello word");
        }
        Ok(())
    }


    #[actix_web::test]
    async fn test_synchronous_task() -> std::io::Result<()> {
        let mut engine = Engine::new(vec![]);
        let result = engine.execute_script(
            r#"
            function task () {
                return {
                    "name": "hello word"
                }
            }
            export default task;
            "#.to_string(),
            serde_json::json!({})
        ).await.unwrap();

        assert!(result.is_some());

        if let Some(result) = result {
            let name = result.get("name").unwrap().as_str().unwrap();
            println!("response -> {}", result.to_string());
            assert_eq!(name, "hello word");
        }
        Ok(())
    }
}

The above is an example I wrote for calling the method, which runs without issues, but it throws an error when I register an asynchronous method.

*** Registering the plugin here will throw an error.***


use std::{cell::RefCell, rc::Rc};

use actix_web::web;
use deno_core::{
    extension, op2, OpState
};
use sea_orm::{DatabaseBackend, FromQueryResult, Statement};

use crate::util::context::Context;


fn json_to_sql_type(values: &Vec<serde_json::Value>) -> Vec<sea_orm::Value> {
    let mut rev: Vec<sea_orm::Value> = vec![];
    for value in values {
        match value {
            serde_json::Value::Array(value) => {}
            serde_json::Value::Bool(value) => {
                let value = sea_orm::Value::Bool(Some(*value));
                rev.push(value);
            }
            serde_json::Value::Number(value) => {
                if value.is_i64() {
                    let value = sea_orm::Value::BigInt(value.as_i64());
                    rev.push(value);
                } else if value.is_f64() {
                    let value = sea_orm::Value::Double(value.as_f64());
                    rev.push(value);
                } else if value.is_u64() {
                    let value = sea_orm::Value::BigUnsigned(value.as_u64());
                    rev.push(value);
                }
            }
            serde_json::Value::Object(value) => {}
            serde_json::Value::String(value) => {
                let value = sea_orm::Value::String(Some(Box::new(value.to_string())));
                rev.push(value);
            }
            serde_json::Value::Null => {}
        }
    }
    return rev;
}


#[op2(async)]
#[serde]
async fn op_context_query_sql(
    #[string] sql: String,
    #[serde] values: Vec<serde_json::Value>,
    state: Rc<RefCell<OpState>>,
) -> Result<Vec<serde_json::Value>, deno_core::anyhow::Error> {

    let mut state = state.borrow_mut();
    let app_data = state.try_take::<web::Data<Context>>().unwrap();

    let database_connection = &app_data.database_connection;

    let param: Vec<sea_orm::sea_query::Value> = json_to_sql_type(&values);

    let result = sea_orm::JsonValue::find_by_statement(Statement::from_sql_and_values(
        DatabaseBackend::MySql,
        sql,
        param,
    ))
    .all(database_connection)
    .await?;
    Ok(result)
}

extension!(
    ext_context,
    ops = [
        op_context_query_sql,
    ],
    esm_entry_point = "script:context",
    esm = [
        dir "src/script/ecmascript",
        "script:context" = "context.js",
    ]
);

pub fn configure_extension(app_data: web::Data<Context>) -> deno_core::Extension {
    let mut extension = ext_context::init_ops_and_esm();
    extension.op_state_fn = Some(Box::new(move |state| {
        state.put(app_data.clone());
    }));
    extension
}

#[cfg(test)]
mod tests {
    use crate::{script::{engine::Engine, ext_context::configure_extension}, util};

    #[actix_web::test]
    async fn test_asynchronous_task() -> std::io::Result<()> {

        let conf = util::conf::get_conf()?;
        let db = util::mysql::get_mysql(&conf).await.unwrap();
        let app_data = actix_web::web::Data::new(util::context::Context {
            database_connection: db.clone(),
            conf: conf.clone()
        });

        let mut engine = Engine::new(vec![configure_extension(app_data)]);
        let result = engine.execute_script(
            r#"
            import context from "script:context";
            async function task () {
                let data = await context.sql.select("select * from user", []);
                Deno.core.print(JSON.stringify(data))
                return data;
            }
            export default task;
            "#.to_string(),
            serde_json::json!({})
        ).await.unwrap();

        assert!(result.is_some());

        if let Some(result) = result {
            println!("response -> {}", result.to_string());
            let length = result.as_array().unwrap().len();
            assert_eq!(length, 1);
        }
        Ok(())
    }
}

context.js

export default {
    sql: {
        select: async (sql, param) => {
            let result = await globalThis.Deno.core.ops.op_context_query_sql(sql, param);
            return result;
        }
    }
}

The error message is as follows.

#
# Fatal error in v8_Promise_Result
# Promise is still pending
#

I suspect that the issue is caused by not entering the deno_core event loop when using let scope = &mut self.v8_runtime.handle_scope() and then calling let result = default_function.call(scope, default_function.into(), &[args]);.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant