0

I am attempting to make an http request and get the response back in ClearScript V8 in my C# controller. When I run the function synchronously, it works fine. But I would like to run it asynchronously to take advantage of non-blocking calls. However, when I attempt to do that, I just get a null response back. I created a couple of helper classes to perform the http client get, and added those to the ClearScript engine. And as stated, running in sync mode, it works great. But when (attempting to ) run on async mode, it just comes back null. Am I even thinking of this correctly? Is this even possible? Or are we locked in because of limitations in ClearScript? Any help is appreciated. Below if my test stub controller:

public class Javascript : Controller
{
    public async Task<IActionResult> Index()
    {
        var hostVariables = new HostVariables();
        V8ScriptEngine engine = new V8ScriptEngine();
        engine.DocumentSettings.AccessFlags = DocumentAccessFlags.None;
        engine.AddHostType(typeof(Console));
        using (HttpClient client = new HttpClient())
        {
            var request = new Request(client);
            engine.AddHostObject("hostVariables", hostVariables);
            engine.AddHostObject("request", request);
            engine.Execute(new DocumentInfo() { Category = ModuleCategory.Standard }, @"
                hostVariables.syncResponse = request.get(""https://www.google.com"").responseText;
                hostVariables.asyncResponse = (await request.getAsync(""https://www.google.com"")).responseText;
            ");
        }

        //hostVariables.syncResponse contains the Google website html
        //hostVariables.asyncResponse is null

        return View();
    }
}
public class HostVariables
{
    public string syncResponse { get; set; }
    public string asyncResponse { get; set; }
}
//HELPER CLASSES TO GIVE CLEARSCRIPT THE ABILITY TO PERFORM HTTP REQUESTS
public class Request
{
    private HttpClient _client;
    public Request(HttpClient client)
    {
        _client = client;
    }
    //ASYNC AND SYNC MODE ARE EXACTLY THE SAME, EXCEPT FOR THE SYNC/ASYNC METHODS
    public Response get(string url)
    {
        var response = new Response();
        var getResponse = _client.GetAsync(url).Result;
        var encoding = ASCIIEncoding.ASCII;
        response.statusCode = (int)getResponse.StatusCode;
        if (getResponse.Content != null)
            response.responseText = getResponse.Content.ReadAsStringAsync().Result;
        if (getResponse.Headers != null)
            response.responseHeaders = getResponse.Headers.Select(x => new ResponseHeader() { key = x.Key, value = x.Value.ToArray() }).ToArray();
        return response;
    }
    public async Task<Response> getAsync(string url)
    {
        var response = new Response();
        var getResponse = await _client.GetAsync(url);
        var encoding = ASCIIEncoding.ASCII;
        response.statusCode = (int)getResponse.StatusCode;
        if (getResponse.Content != null)
            response.responseText = await getResponse.Content.ReadAsStringAsync();
        if (getResponse.Headers != null)
            response.responseHeaders = getResponse.Headers.Select(x => new ResponseHeader() { key = x.Key, value = x.Value.ToArray() }).ToArray();
        return response;
    }
}
public class Response
{
    public string? responseText;
    public ResponseHeader[]? responseHeaders;
    public int? statusCode;
}
public class ResponseHeader
{
    public string? key;
    public string[]? value;
}

1 Answer 1

2

There are two problems with your sample.

First, to allow JavaScript code to await .NET tasks in the desired manner, use V8ScriptEngineFlags.EnableTaskPromiseConversion.

Second, you need to await your JavaScript code just as you would await a .NET async method. Otherwise, you're just kicking off the operation and resuming Index execution before it completes.

Here's a working Index:

public async Task<IActionResult> Index() {
    var hostVariables = new HostVariables();
    using var engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableTaskPromiseConversion);
    using var client = new HttpClient();
    var request = new Request(client);
    engine.AddHostObject("hostVariables", hostVariables);
    engine.AddHostObject("request", request);
    await (Task)engine.Evaluate(new DocumentInfo() { Category = ModuleCategory.Standard }, @"
        hostVariables.syncResponse = request.get(""https://www.google.com"").responseText;
        hostVariables.asyncResponse = (await request.getAsync(""https://www.google.com"")).responseText;
    ");
    return Content(hostVariables.asyncResponse);
}

BTW, if you need to re-execute your JavaScript code, you could wrap it in an async function instead of a module:

dynamic func = engine.Evaluate(@"(async function () {
    hostVariables.syncResponse = request.get(""https://www.google.com"").responseText;
    hostVariables.asyncResponse = (await request.getAsync(""https://www.google.com"")).responseText;
})");
await func();

Note that func becomes invalid when engine is disposed.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for this @BitCortex. That is exactly what I was needing. This is new to me.
One more question @BitCortext - is there a reason you are using "engine.Evaluate" instead of "engine.Execute"?
For JavaScript, Evaluate and Execute do the same thing except that Evaluate can return a value. The difference between them is more significant for VBScript.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.