-
Notifications
You must be signed in to change notification settings - Fork 1
Host Integration API
Roger Johansson edited this page Jan 14, 2026
·
1 revision
How to embed Asynkron.JsEngine in your .NET application and bridge between C# and JavaScript.
flowchart LR
subgraph DotNet[".NET Host"]
Host["C# Code"]
Task["Task<T>"]
end
subgraph Engine["JsEngine"]
Global["Global Object"]
EventLoop["Event Loop"]
Microtasks["Microtask Queue"]
end
subgraph JS["JavaScript"]
Script["JS Code"]
Promise["Promise"]
end
Host -->|"SetGlobalValue"| Global
Host -->|"SetGlobalFunction"| Global
Host -->|"Evaluate"| Script
Task -->|"CreatePromiseFromTask"| Promise
Script -->|"call host fn"| Host
Promise -->|"await"| Script
// Basic creation
await using var engine = new JsEngine();
// With options
await using var engine = new JsEngine(new JsEngineOptions
{
Logger = logger, // ILogger for debug output
DebugMode = true, // Enable verbose logging
ExecutionTimeout = TimeSpan.FromSeconds(30) // Timeout for long-running scripts
});The primary evaluation method. Schedules code on the event loop, supports Promises, timers, and async/await:
// From source string
var result = await engine.Evaluate("1 + 2"); // Returns 3
// Pre-parsed for repeated execution
var program = engine.ParseProgram("x * 2");
var result1 = await engine.Evaluate(program); // Parse once, run many times
var result2 = await engine.Evaluate(program);Waits for all microtasks to complete, then returns the final value of a trailing identifier:
var result = await engine.EvaluateAndAwait(@"
let finalResult = 0;
Promise.resolve(42).then(x => { finalResult = x; });
finalResult;
");
// Returns 42 (not 0) - microtasks drained before reading finalResultFast synchronous evaluation for code without async features:
// Much faster - no event loop overhead
var result = engine.EvaluateSync("Math.sqrt(16)"); // Returns 4
// WARNING: Does NOT support:
// - setTimeout/setInterval
// - Promise resolution
// - async/awaitEvaluates code as an ES module with import/export support:
var exports = await engine.EvaluateModule(@"
export const PI = 3.14159;
export function double(x) { return x * 2; }
");Expose any .NET value as a global variable:
// Primitives
engine.SetGlobalValue("appVersion", "1.0.0");
engine.SetGlobalValue("maxRetries", 3);
engine.SetGlobalValue("debugEnabled", true);
// Objects (converted to JsObject)
engine.SetGlobalValue("config", new Dictionary<string, object>
{
["apiUrl"] = "https://api.example.com",
["timeout"] = 5000
});
// Access in JavaScript
await engine.Evaluate("console.log(appVersion)"); // "1.0.0"
await engine.Evaluate("console.log(config.apiUrl)"); // "https://api.example.com"// Simple handler - just arguments
public delegate JsValue JsSimpleHandler(IReadOnlyList<JsValue> args);
// Host handler - receives 'this' binding
public delegate JsValue JsHostHandler(JsValue thisValue, IReadOnlyList<JsValue> args);
// Async variants - return Task<JsValue>, automatically bridged to Promise
public delegate Task<JsValue> JsAsyncSimpleHandler(IReadOnlyList<JsValue> args);
public delegate Task<JsValue> JsAsyncHostHandler(JsValue thisValue, IReadOnlyList<JsValue> args);// Simple - no 'this' binding
engine.SetGlobalFunction("add", args =>
{
var a = args[0].AsDouble();
var b = args[1].AsDouble();
return a + b;
});
// With 'this' binding
engine.SetGlobalFunction("greet", (thisValue, args) =>
{
var name = args[0].AsString();
return $"Hello, {name}!";
});
// Usage in JavaScript
await engine.Evaluate("add(2, 3)"); // Returns 5
await engine.Evaluate("greet('World')"); // Returns "Hello, World!"Automatically bridges .NET Tasks to JavaScript Promises:
// Simple async
engine.SetGlobalAsyncFunction("fetchData", async args =>
{
var url = args[0].AsString();
var data = await httpClient.GetStringAsync(url);
return (JsValue)data;
});
// With 'this' binding
engine.SetGlobalAsyncFunction("delay", async (thisValue, args) =>
{
var ms = (int)args[0].AsDouble();
await Task.Delay(ms);
return JsValue.Undefined;
});
// Usage in JavaScript
await engine.Evaluate(@"
const data = await fetchData('https://api.example.com/data');
console.log(data);
await delay(1000); // Wait 1 second
");Convert any .NET Task to a JavaScript Promise:
// With type converter
public JsValue CreatePromiseFromTask<T>(Task<T> task, Func<T, JsValue> converter)
// Example: File reading
engine.SetGlobalFunction("readFile", args =>
{
var path = args[0].AsString();
var task = File.ReadAllTextAsync(path);
return engine.CreatePromiseFromTask(task, content => (JsValue)content);
});
// Usage in JavaScript
await engine.Evaluate(@"
const content = await readFile('data.json');
const data = JSON.parse(content);
");Schedule work on the engine's event loop:
engine.ScheduleTask(() =>
{
// This runs on the JS event loop thread
// Safe to interact with JS objects
});Schedule a continuation after an external async operation:
engine.ScheduleAfterTask(
someExternalTask,
() =>
{
// Runs on event loop after task completes
engine.SetGlobalValue("taskResult", result);
}
);Customize how modules are loaded:
// Simple loader - path to source
engine.SetModuleLoader(path =>
{
// Custom resolution logic
var fullPath = Path.Combine(baseDir, path);
return File.ReadAllText(fullPath);
});
// Advanced loader - with referrer context
engine.SetModuleLoader((path, referrer) =>
{
// referrer is the importing module's path
var resolved = ResolveRelative(path, referrer);
return File.ReadAllText(resolved);
});
// Usage in JavaScript
await engine.EvaluateModule(@"
import { helper } from './utils.js'; // Calls your loader
helper();
");await using var engine = new JsEngine();
// Expose configuration
engine.SetGlobalValue("config", new Dictionary<string, object>
{
["apiBase"] = "https://api.example.com",
["timeout"] = 5000
});
// Expose sync utility
engine.SetGlobalFunction("log", args =>
{
Console.WriteLine(args[0].AsString());
return JsValue.Undefined;
});
// Expose async API
engine.SetGlobalAsyncFunction("fetchJson", async args =>
{
var url = $"{config.apiBase}{args[0].AsString()}";
var json = await httpClient.GetStringAsync(url);
return (JsValue)json;
});
// Custom module loader
engine.SetModuleLoader(path => File.ReadAllText($"./scripts/{path}"));
// Run application
await engine.EvaluateModule(@"
import { processData } from './processor.js';
log('Starting...');
const data = await fetchJson('/users');
const result = processData(JSON.parse(data));
log(`Processed ${result.count} items`);
");JsValue value;
// Implicit conversions
value = 42; // Number
value = 3.14; // Number
value = true; // Boolean
value = "hello"; // String
value = someJsObject; // Object
// Explicit
value = JsValue.FromDouble(42.0);
value = JsValue.Undefined;
value = JsValue.Null;JsValue value = await engine.Evaluate("someExpression");
// Type checking
if (value.IsNumber) { ... }
if (value.IsString) { ... }
if (value.IsObject) { ... }
if (value.IsNullish) { ... } // null or undefined
// Extraction
double num = value.AsDouble();
string str = value.AsString();
bool flag = value.AsBoolean();
// Safe extraction with TryGet
if (value.TryGetDouble(out var d)) { ... }
if (value.TryGetString(out var s)) { ... }
if (value.TryGetObject<JsArray>(out var arr)) { ... }
if (value.TryGetCallable(out var func)) { ... }try
{
await engine.Evaluate("throw new Error('Something went wrong')");
}
catch (ThrowSignal signal)
{
// JavaScript throw statement
var errorValue = signal.ThrownValue;
if (errorValue.TryGetObject<JsObject>(out var errorObj))
{
errorObj.TryGetProperty("message", out var message);
Console.WriteLine($"JS Error: {message.AsString()}");
}
}
catch (SyntaxException ex)
{
// Parse error
Console.WriteLine($"Syntax Error: {ex.Message}");
}
catch (OperationCanceledException)
{
// Timeout or cancellation
Console.WriteLine("Script execution timed out");
}- JsValue System - Value representation details
- ES Modules - Module system internals
- Promise & Microtasks - Async integration