Я пытаюсь сделать HTTP-запрос и получить ответ в ClearScript V8 в моем контроллере C#. Когда я запускаю функцию синхронно, она работает нормально. Но я хотел бы запустить его асинхронно, чтобы воспользоваться преимуществами неблокирующих вызовов. Однако, когда я пытаюсь это сделать, я просто получаю нулевой ответ. Я создал пару вспомогательных классов для получения HTTP-клиента и добавил их в движок ClearScript. И как уже говорилось, работа в режиме синхронизации работает отлично. Но когда (попытка) запуститься в асинхронном режиме, он просто возвращает значение null. Я вообще правильно об этом думаю? Возможно ли это вообще? Или мы заперты из-за ограничений ClearScript? Любая помощь приветствуется. Ниже мой контроллер тестовой заглушки:
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;
}
С вашим образцом есть две проблемы.
Во-первых, чтобы разрешить коду JavaScript выполнять задачи await
.NET желаемым образом, используйте V8ScriptEngineFlags.EnableTaskPromiseConversion
.
Во-вторых, вам необходимо await
код JavaScript так же, как и await
метод .NET async
. В противном случае вы просто запускаете операцию и возобновляете Index
выполнение до ее завершения.
Вот рабочий 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);
}
Кстати, если вам нужно повторно выполнить код JavaScript, вы можете обернуть его в функцию async
вместо модуля:
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();
Обратите внимание, что func
становится недействительным, когда engine
удаляется.
Еще один вопрос @BitCortext: есть ли причина, по которой вы используете «engine.Evaluate» вместо «engine.Execute»?
Спасибо за это @BitCortex. Это именно то, что мне было нужно. Это ново для меня.