-
Notifications
You must be signed in to change notification settings - Fork 50
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
Unable to call async JS method and get result #74
Comments
Looks like it is pretty easy to determine if a JS function is async or not. For example: async function test() {
return 1;
}
if(test[Symbol.toStringTag] === 'AsyncFunction'){
console.log('is async');
} else {
console.log('inot async');
} So with that in mind, the await _webView.EvaluateJavaScriptAsync($"(var params = [{paramJson}]; if({methodName}[Symbol.toStringTag] === 'AsyncFunction'){{ async function() {{ var result = await {methodName}(...params}); triggerAsyncCallback(\"{taskId}\", result); }} }} else {{ return {methodName}(...params); }}) We would need to still add the task creation logic. |
@rbrundritt ooooh this looks interesting! I think I was just noticing this same issue while testing the .NET MAUI official HybridWebView, so I'll try out your suggestion and see if I can get it working end-to-end! |
I saw somewhere, I think it was for WebView1, that you could just add an await in front of the async function method name and it should work. I'm going to test that now. That would be much better than using this task ID approach I started out with. |
I'm pretty sure I tried doing |
So I think that probably the JS code has to do the await and then send some kind of message back to C#, and then C# can process the result. |
This looked promising: https://stackoverflow.com/questions/76817407/calling-async-function-in-webview2 But in the end it still didn't work for me. It looks like there is a known bug in WebView2: MicrosoftEdge/WebView2Feedback#2295 There workaround sounds to be to use "DevTools protocol to evaluate your JavaScript" but that doesn't sound ideal. Also not sure if there is similar issues with the other platform browsers. |
On a related not I also found that the I did however find a simple solution. I take the serialized paramJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(paramJson));
return await _webView.EvaluateJavaScriptAsync($"{methodName}(...JSON.parse('[' + atob('{paramJson}') + ']'))"); |
After a bunch of testing I'm going to use the task completion method for now in my app as that seems to be more reliable. I have to testing it a bit more on other platforms. To simplify my code I combined the logic into a single public async Task<string> InvokeJsMethodAsync(string methodName, params object[] paramValues)
{
try
{
if (string.IsNullOrEmpty(methodName))
{
throw new ArgumentException($"The method name cannot be null or empty.", nameof(methodName));
}
//Create a callback.
var callback = new TaskCompletionSource<string>();
var taskId = UniqueId.Get("asyncMapTask");
asyncTaskCallbacks.Add(taskId, callback);
string paramJson = GetParamJson(paramValues);
//Base64 encode parameters to workaround some known issues in the WebView2 control. (html strings and special characters cause EvaluateJavaScriptAsync to fail silently.
paramJson = Convert.ToBase64String(Encoding.UTF8.GetBytes(paramJson));
await _webView.EvaluateJavaScriptAsync($"(async () => {{var paramJson = JSON.parse('[' + atob('{paramJson}') + ']'); var result = ({methodName}[Symbol.toStringTag] === 'AsyncFunction') ? await {methodName}(...paramJson) : {methodName}(...paramJson); triggerAsyncCallback(\"{taskId}\", result); }})()");
return await callback.Task;
}
catch (Exception ex)
{
Debug.WriteLine($"Error invoking async method: {ex.Message}");
return string.Empty;
}
} The following does convert the I'm using the following to generate the private string GetParamJson(object[] paramValues)
{
JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault
};
string paramJson = string.Empty;
if (paramValues != null && paramValues.Length > 0)
{
paramJson = string.Join(", ", paramValues.Select(v => JsonSerializer.Serialize(v, jsonSerializerOptions)));
//New line characters do not currently work with EvaluateJavaScriptAsync. https://github.com/dotnet/maui/issues/11905
//We don't need formatted JSON, and we don't want new line characters in string properties as that's likely to cause issues anyways.
paramJson = paramJson.Replace("\r\n", " ").Replace("\n", " ");
}
return paramJson;
} In my app I don't want null property values to be written. I suspect this may not be the case for everyone, thus it would be good to have a way for people to set both a default serializer and optionally pass in a custom serializer when calling |
Thank you @rbrundritt ! I've been inspired by your code suggestions and I think I have something working that will be part of my PR dotnet/maui#23769 for the official HybridWebView version. I need to test a bunch more things but I do have an end-to-end scenario working with actual async JavaScript code! |
Alright I pushed a major update to dotnet/maui#23769 , in particular this last commit: dotnet/maui@02eb876 I still have cleanup to do, but I've tested it on Windows and Android quite a bit and it seems to work! I'll test iOS/Mac later. @rbrundritt thank you so much for all your help on this! |
I've found that if you call an async JS function you can't wait for the response currently. I managed to work around this, but before making a pull request for this I wanted to show how I was addressing this and get feedback.
Here is a proposal for a new method called
InvokeAsyncJsMethodAsync
which creates a task ID for async JS requests and TaskCompletion (there is some additional code here to address other issues I encountered, like #72 and I also needed custom JsonSerializerOptions)From the JavaScript side of things I need to send a message back when the async task has completed.
Now lets assume I have this simple async JavaScript function:
I can now call this from .NET and wait for it to asynchronously complete.
I've tested this in both Windows and Android with success.
A couple of thoughts for feedback/improvement:
InvokeAsyncJsMethodAsync
sounds weird. Any suggestions?Now that I write the above, I'm thinking that a bit more javascript logic could be added that checks to see if the method is async and if so, then await it. If I could get that working, then we would only need to update the original
InvokeJsMethodAsync
method and make this a seamless update for users.The text was updated successfully, but these errors were encountered: