Cliente de JavaScript SignalR en ASP.NET Core
Por Rachel Appel
La biblioteca cliente de JavaScript de ASP.NET Core SignalR permite a los desarrolladores llamar al código del centro del lado del servidor SignalR.
Instalación del paquete cliente SignalR
La biblioteca de cliente de JavaScript SignalR se entrega como un paquete npm. En las secciones siguientes se describen diferentes formas de instalar la biblioteca cliente.
Instalación con npm
Ejecute los comandos siguientes en la Consola del administrador de paquetes:
npm init -y
npm install @microsoft/signalr
npm instala el contenido del paquete en la carpeta node_modules\@microsoft\signalr\dist\browser. Crea la carpeta wwwroot/lib/signalr. Copie el archivo signalr.js
en la carpeta wwwroot/lib/signalr.
Haga referencia al cliente de JavaScript SignalR en el elemento <script>
. Por ejemplo:
<script src="~/lib/signalr/signalr.js"></script>
Uso de una red de entrega de contenido (CDN)
Para usar la biblioteca cliente sin el requisito previo de npm, haga referencia a una copia hospedada en CDN de la biblioteca cliente. Por ejemplo:
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
La biblioteca cliente está disponible en las siguientes redes CDN:
Instalación con LibMan
LibMan se puede usar para instalar archivos de biblioteca cliente específicos desde la biblioteca cliente hospedada en CDN. Por ejemplo, agregue solo el archivo JavaScript minimizado al proyecto. Para más detalles sobre ese método, consulte Agregue la biblioteca cliente SignalR.
Conexión a un centro
El código siguiente crea e inicia una conexión. El nombre del centro no distingue mayúsculas de minúsculas:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
// Start the connection.
start();
Conexiones entre orígenes (CORS)
Normalmente, los exploradores cargan conexiones desde el mismo dominio que la página solicitada. Sin embargo, hay ocasiones en las que se requiere una conexión a otro dominio.
Al realizar solicitudes entre dominios, el código de cliente debe usar una dirección URL absoluta en lugar de una dirección URL relativa. Para las solicitudes entre dominios, cambie .withUrl("/chathub")
a .withUrl("https://{App domain name}/chathub")
.
Para evitar que un sitio malintencionado lea datos confidenciales de otro sitio, las conexiones entre orígenes están deshabilitadas de forma predeterminada. Para permitir una solicitud entre orígenes, habilite CORS:
using SignalRChat.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
// UseCors must be called before MapHub.
app.UseCors();
app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");
app.Run();
Se debe llamar a UseCors antes de llamar a MapHub.
Llamada a los métodos del centro desde el cliente
Los clientes de JavaScript llaman a métodos públicos en centros a través del método invoke de HubConnection. El método invoke
acepta:
- Nombre del método de concentrador.
- Cualquier argumento definido en el método concentrador.
En el código resaltado siguiente, el nombre del método en el centro es SendMessage
. Los argumentos segundo y tercero que se pasan a invoke
se asignan a los argumentos user
y message
del método del centro:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
La llamada a los métodos del centro desde un cliente solo es compatible cuando se usa el Servicio de Azure en modo SignalRpredeterminado. Para obtener más información, consulta Preguntas más frecuentes (repositorio de GitHub de azure-signalr).
El método invoke
devuelve un objeto Promise
de JavaScript. Promise
se resuelve con el valor devuelto (si existe) cuando el método del servidor devuelve. Si el método del servidor produce un error, Promise
se rechaza con el mensaje de error. Use async
y await
o los métodos then
y catch
de Promise
para controlar estos casos.
Los clientes de JavaScript también pueden llamar a métodos públicos en centros a través del método send de HubConnection
. A diferencia del método invoke
, el método send
no espera una respuesta del servidor. El método send
devuelve un objeto Promise
de JavaScript. El Promise
se resuelve cuando el mensaje ha sido enviado al servidor. Si se produce un error al enviar el mensaje, Promise
se rechaza con el mensaje de error. Use async
y await
o los métodos then
y catch
de Promise
para controlar estos casos.
El uso de send
no espera hasta que el servidor haya recibido el mensaje. Por lo tanto, no es posible devolver datos o errores del servidor.
Llamada a métodos de cliente desde el centro
Para recibir mensajes desde el centro, defina un método mediante el método on del HubConnection
.
- Nombre del método cliente de JavaScript.
- Argumentos que el centro pasa al método .
En el ejemplo siguiente, el nombre del método es ReceiveMessage
. Los nombres de argumento son user
y message
:
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messageList").appendChild(li);
});
El código anterior de connection.on
se ejecuta cuando el código del lado servidor lo llama mediante el método SendAsync:
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
SignalR determina a qué método cliente se debe llamar haciendo coincidir el nombre del método y los argumentos definidos en SendAsync
y connection.on
.
Un procedimiento recomendado es llamar al método start en HubConnection
después de on
. De este modo, se garantiza que los controladores se registran antes de recibir los mensajes.
Registro y control de errores
Use console.error
para mostrar errores en la consola del navegador cuando el cliente no pueda conectarse o enviar un mensaje:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Configure el seguimiento de registros del lado cliente pasando un registrador y un tipo de evento para registrar cuándo se realiza la conexión. Los mensajes se registran con el nivel de registro especificado y versiones posteriores. Los niveles de registro disponibles son los siguientes:
signalR.LogLevel.Error
: mensajes de error. Solo registra mensajes deError
.signalR.LogLevel.Warning
: mensajes de advertencia sobre posibles errores. Registra mensajes deWarning
yError
.signalR.LogLevel.Information
: mensajes de estado sin errores. Registra mensajes deInformation
,Warning
yError
.signalR.LogLevel.Trace
: mensajes de seguimiento. Registra todo, incluidos los datos transportados entre el centro y el cliente.
Use el método configureLogging en HubConnectionBuilder para configurar el nivel de registro. Los mensajes se registran en la consola del explorador:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();
Volver a conectar clientes
Volver a conectarse automáticamente
El cliente JavaScript para SignalR puede configurarse para reconectarse automáticamente usando el método WithAutomaticReconnect en HubConnectionBuilder. No se volverá a conectar automáticamente de forma predeterminada.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect()
.build();
Sin ningún parámetro, WithAutomaticReconnect configura el cliente para que espere 0, 2, 10 y 30 segundos respectivamente antes de intentar cada intento de reconexión. Después de cuatro intentos fallidos, deja de intentar volver a conectarse.
Antes de iniciar cualquier intento de reconexión, el HubConnection
:
- Realiza la transición al estado
HubConnectionState.Reconnecting
y desencadena sus devoluciones de llamada deonreconnecting
. - No pasa al estado
Disconnected
y desencadena sus devoluciones de llamada deonclose
como unHubConnection
sin reconexión automática configurada.
El enfoque de reconexión proporciona una oportunidad para:
- Advertir a los usuarios de que se ha perdido la conexión.
- Deshabilitar elementos de la interfaz de usuario.
connection.onreconnecting(error => {
console.assert(connection.state === signalR.HubConnectionState.Reconnecting);
document.getElementById("messageInput").disabled = true;
const li = document.createElement("li");
li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
document.getElementById("messageList").appendChild(li);
});
Si el cliente se reconecta con éxito en sus cuatro primeros intentos, el HubConnection
vuelve al estado Connected
y desencadena sus devoluciones de llamada de onreconnected
. Esto proporciona la oportunidad de informar a los usuarios de la conexión que se ha restablecido.
Dado que la conexión parece totalmente nueva para el servidor, se proporciona un nuevo connectionId
a la devolución de llamada de onreconnected
.
El parámetro connectionId
de la devolución de llamada onreconnected
es indefinido si el HubConnection
está configurado para omitir la negociación.
connection.onreconnected(connectionId => {
console.assert(connection.state === signalR.HubConnectionState.Connected);
document.getElementById("messageInput").disabled = false;
const li = document.createElement("li");
li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
document.getElementById("messageList").appendChild(li);
});
withAutomaticReconnect
no configurará el HubConnection
para reintentar los fallos de arranque iniciales, por lo que los fallos de arranque deben controlarse manualmente:
async function start() {
try {
await connection.start();
console.assert(connection.state === signalR.HubConnectionState.Connected);
console.log("SignalR Connected.");
} catch (err) {
console.assert(connection.state === signalR.HubConnectionState.Disconnected);
console.log(err);
setTimeout(() => start(), 5000);
}
};
Si el cliente no se reconecta con éxito en sus cuatro primeros intentos, el HubConnection
pasa al estado Disconnected
y desencadena sus devoluciones de llamada onclose. Esto proporciona una oportunidad para notificar a los usuarios:
- La conexión se ha perdido definitivamente.
- Pruebe a actualizar la página:
connection.onclose(error => {
console.assert(connection.state === signalR.HubConnectionState.Disconnected);
document.getElementById("messageInput").disabled = true;
const li = document.createElement("li");
li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
document.getElementById("messageList").appendChild(li);
});
Para configurar un número personalizado de intentos de reconexión antes de desconectar o cambiar el tiempo de reconexión, withAutomaticReconnect
acepta una matriz de números que representa el retraso en milisegundos para esperar antes de iniciar cada intento de reconexión.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect([0, 0, 10000])
.build();
// .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior
El ejemplo anterior configura el HubConnection
para que empiece a intentar reconectarse inmediatamente después de que se pierda la conexión. La configuración predeterminada también espera cero segundos para intentar volver a conectarse.
Si se produce un error en el primer intento de reconexión, el segundo intento de reconexión también se inicia inmediatamente en lugar de esperar 2 segundos con la configuración predeterminada.
Si se produce un error en el segundo intento de reconexión, el tercer intento de reconexión se inicia en 10 segundos, que es el mismo que la configuración predeterminada.
El tiempo de reconexión configurado difiere del comportamiento predeterminado al detenerse tras el fallo del tercer intento de reconexión en lugar de intentar un nuevo intento de reconexión en otros 30 segundos.
Para un mayor control sobre el tiempo y el número de intentos de reconexión automática, withAutomaticReconnect
acepta un objeto que implemente la interfaz IRetryPolicy
, que tiene un único método llamado nextRetryDelayInMilliseconds
.
nextRetryDelayInMilliseconds
toma un único argumento con el tipo RetryContext
. El RetryContext
tiene tres propiedades: previousRetryCount
, elapsedMilliseconds
y retryReason
que son number
, number
y Error
respectivamente. Antes del primer intento de reconexión, tanto previousRetryCount
como elapsedMilliseconds
serán cero, y retryReason
será el Error que provocó la pérdida de conexión. Después de cada intento de reconexión fallido, previousRetryCount
se incrementará en uno, elapsedMilliseconds
se actualizará para reflejar la cantidad de tiempo empleado en la reconexión hasta el momento en milisegundos, y retryReason
será el Error que causó el último intento de reconexión fallido.
nextRetryDelayInMilliseconds
debe devolver un número que represente el número de milisegundos que hay que esperar antes del siguiente intento de reconexión o null
si HubConnection
debe dejar de reconectarse.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: retryContext => {
if (retryContext.elapsedMilliseconds < 60000) {
// If we've been reconnecting for less than 60 seconds so far,
// wait between 0 and 10 seconds before the next reconnect attempt.
return Math.random() * 10000;
} else {
// If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
return null;
}
}
})
.build();
Como alternativa, se puede escribir código que vuelva a conectar al cliente manualmente, como se demuestra en la siguiente sección.
Volver a conectarse manualmente
En el código siguiente se muestra un enfoque típico de reconexión manual:
- Se crea una función (en este caso, la función
start
) para iniciar la conexión. - Llame a la función
start
en el controlador de eventosonclose
de la conexión.
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
Las implementaciones de producción suelen usar un retroceso exponencial o reintentar un número especificado de veces.
Pestaña de suspensión del explorador
Algunos exploradores tienen una característica de bloqueo de pestañas o de suspensión para reducir el uso de recursos del equipo para pestañas inactivas. Esto puede provocar el cierre de conexiones de SignalR y puede dar lugar a una experiencia de usuario no deseada. Los exploradores usan heurística para averiguar si se debe poner en suspensión una pestaña, como:
- Reproducir audio
- Mantener un bloqueo web
- Mantener un bloqueo
IndexedDB
- Estar conectado a un dispositivo USB
- Capturar vídeo o audio
- Crear un reflejo
- Capturar una ventana o pantalla
La heurística del explorador puede cambiar con el tiempo y puede diferir entre exploradores. Compruebe la matriz de compatibilidad y descubra qué método funciona mejor para sus escenarios.
Para evitar la suspensión de una aplicación, la aplicación debería estar desencadenando una de las heurísticas que usa el navegador.
En el ejemplo de código siguiente se muestra cómo usar un bloqueo web para mantener activa una pestaña y evitar un cierre de conexión inesperado.
var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
const promise = new Promise((res) => {
lockResolver = res;
});
navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
return promise;
});
}
Para el ejemplo de código anterior:
- Los bloqueos web son experimentales. La comprobación condicional confirma que el explorador admite bloqueos web.
- El solucionador de promesas,
lockResolver
, se almacena para que el bloqueo se pueda liberar cuando sea aceptable que la pestaña se suspenda. - Al cerrar la conexión, el bloqueo se libera mediante una llamada a
lockResolver()
. Cuando se libera el bloqueo, la pestaña puede suspenderse.
Recursos adicionales
- Vea o descargue el código de ejemplo (cómo descargarlo)
- Referencia de API de JavaScript
- Tutorial de JavaScript
- Tutorial de WebPack y TypeScript
- Concentradores
- Cliente .NET
- Publicar en Azure
- Solicitudes de origen cruzado (CORS)
- Documentación del servicio sin servidor de Azure SignalR
- Solución de problemas de conexión
Por Rachel Appel
La biblioteca cliente de JavaScript de ASP.NET Core SignalR permite a los desarrolladores llamar al código del centro del lado del servidor.
Vea o descargue el código de ejemplo (cómo descargarlo)
Instalación del paquete cliente SignalR
La biblioteca de cliente de JavaScript SignalR se entrega como un paquete npm. En las secciones siguientes se describen diferentes formas de instalar la biblioteca cliente.
Instalación con npm
Para Visual Studio, ejecute los siguientes comandos desde la consola del administrador de paquetes mientras se encuentra en la carpeta raíz. Para Visual Studio Code, ejecute los siguientes comandos desde el Terminal integrado.
npm init -y
npm install @microsoft/signalr
npm instala el contenido del paquete en la carpeta node_modules\@microsoft\signalr\dist\browser. Crea una carpeta denominada signalr en la carpeta wwwroot\lib. Copia el archivo signalr.js
en la carpeta wwwroot\lib\signalr.
Haga referencia al cliente de JavaScript SignalR en el elemento <script>
. Por ejemplo:
<script src="~/lib/signalr/signalr.js"></script>
Uso de una red de entrega de contenido (CDN)
Para usar la biblioteca cliente sin el requisito previo de npm, haga referencia a una copia hospedada en CDN de la biblioteca cliente. Por ejemplo:
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>
La biblioteca cliente está disponible en las siguientes redes CDN:
Instalación con LibMan
LibMan se puede usar para instalar archivos de biblioteca cliente específicos desde la biblioteca cliente hospedada en CDN. Por ejemplo, agregue solo el archivo JavaScript minimizado al proyecto. Para más detalles sobre ese método, consulte Agregue la biblioteca cliente SignalR.
Conexión a un centro
El código siguiente crea e inicia una conexión. El nombre del centro no distingue mayúsculas de minúsculas:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
// Start the connection.
start();
Conexiones entre orígenes
Normalmente, los exploradores cargan conexiones desde el mismo dominio que la página solicitada. Sin embargo, hay ocasiones en las que se requiere una conexión a otro dominio.
Importante
El código de cliente debe usar una dirección URL absoluta en lugar de una dirección URL relativa. Cambio de .withUrl("/chathub")
a .withUrl("https://myappurl/chathub")
.
Para evitar que un sitio malintencionado lea datos confidenciales de otro sitio, las conexiones entre orígenes están deshabilitadas de forma predeterminada. Para permitir una solicitud de origen cruzado, habilítela en la clase Startup
:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;
namespace SignalRChat
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR();
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins("https://example.com")
.AllowCredentials();
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chathub");
});
}
}
}
Llamada a los métodos del centro desde el cliente
Los clientes de JavaScript llaman a métodos públicos en centros a través del método invoke de HubConnection. El método invoke
acepta:
- Nombre del método de concentrador.
- Cualquier argumento definido en el método concentrador.
En el siguiente ejemplo, el nombre del método en el centro es SendMessage
. Los argumentos segundo y tercero que se pasan a invoke
se asignan a los argumentos user
y message
del método del centro:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Nota:
La llamada a los métodos del concentrador desde un cliente solo es compatible cuando se usa el Servicio de Azure SignalR en modo Predeterminado. Para obtener más información, consulta Preguntas más frecuentes (repositorio de GitHub de azure-signalr).
El método invoke
devuelve un objeto Promise
de JavaScript. Promise
se resuelve con el valor devuelto (si existe) cuando el método del servidor devuelve. Si el método del servidor produce un error, Promise
se rechaza con el mensaje de error. Use async
y await
o los métodos then
y catch
de Promise
para controlar estos casos.
Los clientes de JavaScript también pueden llamar a métodos públicos en centros a través del método send de HubConnection
. A diferencia del método invoke
, el método send
no espera una respuesta del servidor. El método send
devuelve un objeto Promise
de JavaScript. El Promise
se resuelve cuando el mensaje ha sido enviado al servidor. Si se produce un error al enviar el mensaje, Promise
se rechaza con el mensaje de error. Use async
y await
o los métodos then
y catch
de Promise
para controlar estos casos.
Nota
El uso de send
no espera a que el servidor haya recibido el mensaje. Por lo tanto, no es posible devolver datos o errores del servidor.
Llamada a métodos de cliente desde el centro
Para recibir mensajes desde el centro, defina un método mediante el método on del HubConnection
.
- Nombre del método cliente de JavaScript.
- Argumentos que el centro pasa al método .
En el ejemplo siguiente, el nombre del método es ReceiveMessage
. Los nombres de argumento son user
y message
:
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = `${user}: ${message}`;
document.getElementById("messageList").appendChild(li);
});
El código anterior de connection.on
se ejecuta cuando el código del lado servidor lo llama mediante el método SendAsync:
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
SignalR determina a qué método cliente se debe llamar haciendo coincidir el nombre del método y los argumentos definidos en SendAsync
y connection.on
.
Nota
Un procedimiento recomendado es llamar al método start en HubConnection
después de on
. Al hacerlo, se asegura de que los controladores se registren antes de que se reciban mensajes.
Registro y control de errores
Use try
y catch
con async
y await
o el método catch
de Promise
para controlar los errores del lado del cliente. Use console.error
para generar errores en la consola del explorador:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Configure el seguimiento de registros del lado cliente pasando un registrador y un tipo de evento para registrar cuándo se realiza la conexión. Los mensajes se registran con el nivel de registro especificado y versiones posteriores. Los niveles de registro disponibles son los siguientes:
signalR.LogLevel.Error
: mensajes de error. Solo registra mensajes deError
.signalR.LogLevel.Warning
: mensajes de advertencia sobre posibles errores. Registra mensajes deWarning
yError
.signalR.LogLevel.Information
: mensajes de estado sin errores. Registra mensajes deInformation
,Warning
yError
.signalR.LogLevel.Trace
: mensajes de seguimiento. Registra todo, incluidos los datos transportados entre el centro y el cliente.
Use el método configureLogging en HubConnectionBuilder para configurar el nivel de registro. Los mensajes se registran en la consola del explorador:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.configureLogging(signalR.LogLevel.Information)
.build();
Volver a conectar clientes
Volver a conectarse automáticamente
El cliente de JavaScript para SignalR puede configurarse para reconectarse automáticamente usando el método withAutomaticReconnect
en HubConnectionBuilder. No se volverá a conectar automáticamente de forma predeterminada.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect()
.build();
Sin ningún parámetro, withAutomaticReconnect()
configura el cliente para que espere 0, 2, 10 y 30 segundos respectivamente antes de intentar cada intento de reconexión, deteniéndose tras cuatro intentos fallidos.
Antes de iniciar cualquier intento de reconexión, HubConnection
pasará al estado HubConnectionState.Reconnecting
y activará sus devoluciones de llamada de onreconnecting
en lugar de pasar al estado Disconnected
y desencadenar sus devoluciones de llamada de onclose
como un HubConnection
sin reconexión automática configurada. Esto proporciona una oportunidad para advertir a los usuarios de que se ha perdido la conexión y deshabilitar los elementos de la interfaz de usuario.
connection.onreconnecting(error => {
console.assert(connection.state === signalR.HubConnectionState.Reconnecting);
document.getElementById("messageInput").disabled = true;
const li = document.createElement("li");
li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
document.getElementById("messageList").appendChild(li);
});
Si el cliente se reconecta con éxito en sus cuatro primeros intentos, el HubConnection
vuelve al estado Connected
y desencadena sus devoluciones de llamada de onreconnected
. Esto proporciona la oportunidad de informar a los usuarios de la conexión que se ha restablecido.
Dado que la conexión parece totalmente nueva para el servidor, se proporciona un nuevo connectionId
a la devolución de llamada de onreconnected
.
Advertencia
El parámetro connectionId
de la devolución de llamada onreconnected
será indefinido si HubConnection
se configuró para omitir la negociación.
connection.onreconnected(connectionId => {
console.assert(connection.state === signalR.HubConnectionState.Connected);
document.getElementById("messageInput").disabled = false;
const li = document.createElement("li");
li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
document.getElementById("messageList").appendChild(li);
});
withAutomaticReconnect()
no configurará HubConnection
para reintentar los fallos de arranque iniciales, por lo que los fallos de arranque deben controlarse manualmente:
async function start() {
try {
await connection.start();
console.assert(connection.state === signalR.HubConnectionState.Connected);
console.log("SignalR Connected.");
} catch (err) {
console.assert(connection.state === signalR.HubConnectionState.Disconnected);
console.log(err);
setTimeout(() => start(), 5000);
}
};
Si el cliente no se reconecta con éxito en sus cuatro primeros intentos, HubConnection
pasa al estado Disconnected
y desencadena sus devoluciones de llamada onclose. Esto proporciona la oportunidad de informar a los usuarios de que la conexión se ha perdido permanentemente y recomendar la actualización de la página:
connection.onclose(error => {
console.assert(connection.state === signalR.HubConnectionState.Disconnected);
document.getElementById("messageInput").disabled = true;
const li = document.createElement("li");
li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
document.getElementById("messageList").appendChild(li);
});
Para configurar un número personalizado de intentos de reconexión antes de desconectar o cambiar el tiempo de reconexión, withAutomaticReconnect
acepta una matriz de números que representa el retraso en milisegundos para esperar antes de iniciar cada intento de reconexión.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect([0, 0, 10000])
.build();
// .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior
El ejemplo anterior configura el HubConnection
para que empiece a intentar reconectarse inmediatamente después de que se pierda la conexión. Esto también es cierto para la configuración predeterminada.
Si se produce un error en el primer intento de reconexión, el segundo intento de reconexión también se iniciará inmediatamente en lugar de esperar 2 segundos como lo haría en la configuración predeterminada.
Si se produce un error en el segundo intento de reconexión, el tercer intento de reconexión se iniciará en 10 segundos, como la configuración predeterminada.
Después, el comportamiento personalizado vuelve a desviarse del comportamiento predeterminado al detenerse tras el fracaso del tercer intento de reconexión en lugar de intentar una reconexión más en otros 30 segundos como haría en la configuración predeterminada.
Si quiere aún más control sobre el tiempo y el número de intentos de reconexión automática, withAutomaticReconnect
acepta un objeto que implemente la interfaz IRetryPolicy
, que tiene un único método llamado nextRetryDelayInMilliseconds
.
nextRetryDelayInMilliseconds
toma un único argumento con el tipo RetryContext
. El RetryContext
tiene tres propiedades: previousRetryCount
, elapsedMilliseconds
y retryReason
que son number
, number
y Error
respectivamente. Antes del primer intento de reconexión, tanto previousRetryCount
como elapsedMilliseconds
serán cero, y retryReason
será el Error que provocó la pérdida de conexión. Después de cada intento de reconexión fallido, previousRetryCount
se incrementará en uno, elapsedMilliseconds
se actualizará para reflejar la cantidad de tiempo empleado en la reconexión hasta el momento en milisegundos, y retryReason
será el Error que causó el último intento de reconexión fallido.
nextRetryDelayInMilliseconds
debe devolver un número que represente el número de milisegundos que hay que esperar antes del siguiente intento de reconexión o null
si HubConnection
debe dejar de reconectarse.
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: retryContext => {
if (retryContext.elapsedMilliseconds < 60000) {
// If we've been reconnecting for less than 60 seconds so far,
// wait between 0 and 10 seconds before the next reconnect attempt.
return Math.random() * 10000;
} else {
// If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
return null;
}
}
})
.build();
Como alternativa, puede escribir código que vuelva a conectar el cliente manualmente, como se muestra en Reconexión manual.
Volver a conectarse manualmente
En el código siguiente se muestra un enfoque típico de reconexión manual:
- Se crea una función (en este caso, la función
start
) para iniciar la conexión. - Llame a la función
start
en el controlador de eventosonclose
de la conexión.
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => {
await start();
});
Las implementaciones de producción suelen usar un retroceso exponencial o reintentar un número especificado de veces.
Pestaña de suspensión del explorador
Algunos exploradores tienen una característica de bloqueo de pestañas o de suspensión para reducir el uso de recursos del equipo para pestañas inactivas. Esto puede provocar el cierre de conexiones de SignalR y puede dar lugar a una experiencia de usuario no deseada. Los exploradores usan heurística para averiguar si se debe poner en suspensión una pestaña, como:
- Reproducir audio
- Mantener un bloqueo web
- Mantener un bloqueo
IndexedDB
- Estar conectado a un dispositivo USB
- Capturar vídeo o audio
- Crear un reflejo
- Capturar una ventana o pantalla
Nota
Esta heurística puede cambiar con el tiempo o diferir entre navegadores. Compruebe la matriz de compatibilidad y descubra qué método funciona mejor para sus escenarios.
Para evitar la suspensión de una aplicación, la aplicación debería estar desencadenando una de las heurísticas que usa el navegador.
En el ejemplo de código siguiente se muestra cómo usar un bloqueo web para mantener activa una pestaña y evitar un cierre de conexión inesperado.
var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
const promise = new Promise((res) => {
lockResolver = res;
});
navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
return promise;
});
}
Para el ejemplo de código anterior:
- Los bloqueos web son experimentales. La comprobación condicional confirma que el explorador admite bloqueos web.
- El solucionador de promesas (
lockResolver
) se almacena para que el bloqueo se pueda liberar cuando sea aceptable que la pestaña se suspenda. - Al cerrar la conexión, el bloqueo se libera mediante una llamada a
lockResolver()
. Cuando se libera el bloqueo, la pestaña puede suspenderse.