Calling JsRuntime.Current.InvokeAsync from C# in HTML part of a component results in Uncaught (in promise)
JSInterop does not work in Async mode. I did manage to get the code to work by down-casting to IJSInProcessRuntime and then calling Invoke instead of InvokeAsync, but this still felt like a bug so I thought I should report it.
I have the following code in a Component which is supposed to show one section of code if a token is present in local storage, and another if not.
@inject AuthService AuthService
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
@if (IsLoggedIn())
{
<div class="dropdown">
<a class="dropdown-toggle text-light">
Welcome User
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#"><i class="fa fa-user"></i> Edit Profile</a>
<div class="divider"></div>
<a class="dropdown-item" href="#"><i class="fa fa-sign-out"></i> Log out</a>
</div>
</div>
}
else
{
<form class="form-inline my-2 my-lg-0" onsubmit="@LogIn"> @*Change to bind-to-oninput when it is fixed*@
<input class="form-control mr-sm-2" name="username" placeholder="Username" type="text" required bind="@Username" />
<input class="form-control mr-sm-2" name="password" placeholder="Password" type="text" required bind="@Password" />
<button class="btn btn-success my-2 my-sm-0" type="submit" disabled="@_loginDisabled">Login</button>
</form>
}
</nav>
@functions {
// Other methods and properties left out for brevity.
bool IsLoggedIn()
{
// Using .Result as await cannot be used in the Razor HTML
return AuthService.IsLoggedIn().Result;
}
}
And then inside AuthService.cs
public async Task<bool> IsLoggedIn()
{
try
{
var token = await JSRuntime.Current.InvokeAsync<string>("jsinterop.getLocalStorageItem", TOKEN_KEY);
return !string.IsNullOrWhiteSpace(token);
}
catch (Exception e)
{
Console.WriteLine($"public async Task<bool> IsLoggedIn()': {e.Message}");
return false;
}
}
Load the component and look in the console.
The following console output:
blazor.webassembly.js:1 WASM: 13
d.printErr @ blazor.webassembly.js:1
blazor.webassembly.js:1 Uncaught (in promise) abort(13). Build with -s ASSERTIONS=1 for more info.
Using Visual Studio 2017 15.8.6 and Blazor 0.6
I think the problem that we can not use Task.Result is the limitation of Blazor by design.
I think, your sample code cause "deadlock" like this figure. (from this post)

I'll rewrite your sample code as follow.
Initialize the status of logged in or not, only once at the component initialization.
@functions {
bool IsLoggedIn;
protected override async Task OnInitAsync()
{
this.IsLoggedIn = await AuthService.IsLoggedIn();
}
}
And reference it.
@inject AuthService AuthService
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
@if (IsLoggedIn)
{
...
If you have to call JavaScript synchronously, I can provide the information to you about workaround / hack to do it.
See also: https://dev.to/j_sakamoto/workaround-how-to-resolve-local-time-zone-on-blazor-spa-v05x-foi
Thanks for the answer, @jsakamoto.
Yes, the basic fact is that the component's rendering logic must be synchronous. This is the desired design, because unlike a server-side HTML rendering process, the user is already looking at your UI and is waiting for it to update. If the rendering could take an arbitrary amount of time, the user would have no idea what was going on. So if you need to perform an async task, you need to model that in your UI. That is, start the async task in one of the lifecycle methods (e.g., OnInitAsync) and use your synchronous rendering to display some kind of "loading" indicator until that task is completed.
So if you want to use synchronous JS interop during rendering that is technically OK, but it might be better still to move it to async JS interop outside the render logic.
Most helpful comment
I think the problem that we can not use Task.Result is the limitation of Blazor by design.
I think, your sample code cause "deadlock" like this figure. (from this post)
I'll rewrite your sample code as follow.
Initialize the status of logged in or not, only once at the component initialization.
And reference it.
If you have to call JavaScript synchronously, I can provide the information to you about workaround / hack to do it.
See also: https://dev.to/j_sakamoto/workaround-how-to-resolve-local-time-zone-on-blazor-spa-v05x-foi