deno_core
deno_core copied to clipboard
How should I register an asynchronous method and call this asynchronous method from JS?
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]);.