ASP.NET Core SignalR JavaScript 用戶端

作者:Rachel Appel

ASP.NET Core SignalR JavaScript 用戶端程式庫可讓開發人員呼叫伺服器端 SignalR 中樞程式碼。

安裝 SignalR 用戶端套件

SignalR JavaScript 用戶端程式庫會以 npm 套件形式傳遞。 下列各節概述不同的用戶端程式庫安裝方式。

使用 npm 安裝

從 [套件管理員主控台] 中,執行下列命令:

npm init -y
npm install @microsoft/signalr

npm 會在 node_modules\@microsoft\signalr\dist\browser 資料夾中安裝套件內容。 建立 wwwroot/lib/signalr 資料夾。 將 signalr.js 檔案複製至 wwwroot/lib/signalr 資料夾。

參考 <script> 元素中的 SignalR JavaScript 用戶端。 例如:

<script src="~/lib/signalr/signalr.js"></script>

使用內容傳遞網路 (CDN)

若要在沒有 npm 必要條件的情況下使用用戶端程式庫,請參考用戶端程式庫的 CDN 裝載複本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

在下列 CDN 上,可以提供用戶端程式庫:

使用 LibMan 安裝

LibMan 可以用來安裝 CDN 裝載用戶端程式庫中的特定用戶端程式庫檔案。 例如,只將縮小的 JavaScript 檔案新增至專案。 如需該方式的詳細資料,請參閱新增 SignalR 用戶端程式庫

連線至中樞

下列程式碼會建立並啟動連線。 中樞的名稱不區分大小寫:

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();

跨原始來源連線 (CORS)

瀏覽器一般會從與所要求頁面相同的網域載入連線。 不過,有時候需要連線至另一個網域。

提出跨網域要求時,用戶端程式碼「必須」使用絕對 URL,而不是相對 URL。 針對跨網域要求,將 .withUrl("/chathub") 變更為 .withUrl("https://{App domain name}/chathub")

若要防止惡意網站從另一個網站讀取敏感性資料,預設會停用跨原始來源連線。 若要允許跨原始來源要求,請啟用 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();

必須先呼叫 UseCors,才能呼叫 MapHub

從用戶端呼叫中樞方法

JavaScript 用戶端會透過 HubConnectioninvoke 方法來呼叫中樞上的公用方法。 invoke 方法可接受:

  • 中樞方法的名稱。
  • 中樞方法中所定義的任何引數。

在下列醒目提示的程式碼中,中樞上的方法名稱為 SendMessage。 傳遞至 invoke 的第二個和第三個引數會對應至中樞方法的 usermessage 引數:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

只有以預設模式使用 Azure SignalR Service 時,才支援從用戶端呼叫中樞方法。 如需詳細資訊,請參閱常見問題集 (azure-signalr GitHub 存放庫)

invoke 方法會傳回 JavaScript Promise。 傳回伺服器上的方法時,會使用傳回值來解析 Promise。 如果伺服器上的方法擲回錯誤,則會拒絕 Promise,並顯示錯誤訊息。 使用 asyncawaitPromisethencatch 方法來處理這些案例。

JavaScript 用戶端也可以透過 HubConnectionsend 方法,以在中樞上呼叫公用方法。 與 invoke 方法不同,send 方法不會等候來自伺服器的回應。 send 方法會傳回 JavaScript Promise。 已將訊息傳送至伺服器時,會解析 Promise。 如果傳送訊息時發生錯誤,則會拒絕 Promise,並顯示錯誤訊息。 使用 asyncawaitPromisethencatch 方法來處理這些案例。

使用 send「不」會等到伺服器收到訊息。 因此,無法從伺服器傳回資料或錯誤。

從中樞呼叫用戶端方法

若要從中樞接收訊息,請使用 HubConnectionon 方法來定義方法。

  • JavaScript 用戶端方法的名稱。
  • 中樞傳遞至方法的引數。

在下列範例中,方法名稱為 ReceiveMessage。 引數名稱為 usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

connection.on 中的上述程式碼會在伺服器端程式碼使用 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 會比對 SendAsyncconnection.on 中所定義的方法名稱和引數,來判斷要呼叫的用戶端方法。

「最佳做法」是在 on 後面於 HubConnection 上呼叫 start 方法。 這麼做可確保先在收到任何訊息之前註冊處理常式。

錯誤處理和記錄

用戶端無法連線或傳送訊息時,請使用 console.error 以將錯誤輸出至瀏覽器的主控台:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

建立連線時,傳遞要記錄之事件的記錄器和類型,以記錄用戶端記錄追蹤。 會以指定的記錄層級和更高層級來記錄訊息。 可用的記錄層級如下:

  • signalR.LogLevel.Error:錯誤訊息。 僅記錄 Error 訊息。
  • signalR.LogLevel.Warning:潛在錯誤的相關警告訊息。 記錄 WarningError 訊息。
  • signalR.LogLevel.Information:狀態訊息,而且未發生錯誤。 記錄 InformationWarningError 訊息。
  • signalR.LogLevel.Trace:追蹤訊息。 記錄所有項目,包括中樞與用戶端之間傳輸的資料。

HubConnectionBuilder 上使用 configureLogging 方法,以設定記錄層級。 訊息會記錄至瀏覽器主控台:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

重新連線用戶端

自動重新連線

SignalR 的 JavaScript 用戶端可以設定為使用 HubConnectionBuilder 上的 WithAutomaticReconnect 方法來自動重新連線。 其預設不會自動重新連線。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

如果不使用任何參數,則 WithAutomaticReconnect 會將用戶端設定為在嘗試每次重新連線嘗試之前分別等待 0、2、10 和 30 秒。 四次嘗試失敗之後,即會停止嘗試重新連線。

開始任何重新連線嘗試之前,HubConnection

  • 轉換至 HubConnectionState.Reconnecting 狀態,並引發其 onreconnecting 回呼。
  • 不會轉換至 Disconnected 狀態,並在未設定自動重新連線的情況下觸發其 onclose 回呼 (例如 HubConnection)。

重新連線方式可讓您:

  • 向使用者警告已中斷連線。
  • 停用 UI 元素。
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);
});

如果用戶端在前四次嘗試中成功重新連線,則 HubConnection 會轉換回 Connected 狀態,並引發其 onreconnected 回呼。 這可讓您向使用者通知已重新建立連線。

因為該連線對伺服器來說是全新的,所以會將新的 connectionId 提供給 onreconnected 回呼。

如果 HubConnection 設定為跳過交涉,則「未定義」onreconnected 回呼的 connectionId 參數。

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 不會將 HubConnection 設定為重試初始啟動失敗,因此需要手動進行處理:

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);
    }
};

如果用戶端在前四次嘗試中未成功重新連線,則 HubConnection 會轉換至 Disconnected 狀態,並引發其 onclose 回呼。 這可讓您通知使用者:

  • 已永久中斷連線。
  • 請嘗試重新整理頁面:
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);
});

若要在中斷連線或變更重新連線時間之前設定自訂重新連線嘗試次數,withAutomaticReconnect 接受一個數字陣列,表示在開始每次重新連線嘗試之前要等待的延遲時間 (以毫秒為單位)。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

上述範例會將 HubConnection 設定為在連線遺失後立即開始嘗試重新連線。 預設設定也會等待零秒,以嘗試重新連線。

如果第一次重新連線嘗試失敗,則第二次重新連線嘗試也會立即啟動,而不是使用預設設定來等待 2 秒。

如果第二次重新連線嘗試失敗,則第三次重新連線嘗試會在 10 秒內開始,而這與預設設定相同。

所設定的重新連線時間與預設行為不同,原因是在第三次重新連線嘗試失敗後停止,而不是在另一個 30 秒內再嘗試一次重新連線嘗試。

為了更充分掌控自動重新連線嘗試的時間和次數,withAutomaticReconnect 接受一個實作 IRetryPolicy 介面的物件,該介面有名為 nextRetryDelayInMilliseconds 的單一方法。

nextRetryDelayInMilliseconds 會採用類型為 RetryContext 的單一引數。 RetryContext 有三個屬性:previousRetryCountelapsedMillisecondsretryReason,分別是 numbernumberError。 在第一次重新連線嘗試之前,previousRetryCountelapsedMilliseconds 都會是零,而 retryReason 會是造成連線中斷的「錯誤」。 每次重試失敗之後,previousRetryCount 將會遞增一次,elapsedMilliseconds 將會更新以反映到目前為止重新連線所花費的時間量 (毫秒),而 retryReason 會是造成上次重新連線嘗試失敗的「錯誤」。

nextRetryDelayInMilliseconds 必須傳回一個數字,代表下一次重新連線嘗試之前所要等待的毫秒數,或者,如果 HubConnection 應該停止重新連線,則傳回 null

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();

或者,您可以撰寫程式碼,以手動重新連線用戶端,如下節所示範。

手動重新連線

下列程式碼示範一般手動重新連線方式:

  1. 會建立函數 (在此案例中,為 start 函數) 來啟動連線。
  2. 在連線的 onclose 事件處理常式中,呼叫 start 函數。
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

生產實作通常會使用指數輪詢,或重試指定的次數。

瀏覽器睡眠索引標籤

某些瀏覽器具有索引標籤凍結或睡眠功能,以減少非使用中索引標籤的電腦資源使用量。 這可能會導致關閉 SignalR 連線,而且可能會導致不必要的使用者體驗。 瀏覽器會使用啟發學習法來找出索引標籤是否應該進入睡眠狀態,例如:

  • 播放音訊
  • 保留 Web 鎖定
  • 保留 IndexedDB 鎖定
  • 連線至 USB 裝置
  • 擷取視訊或音訊
  • 進行鏡像
  • 擷取視窗或顯示

瀏覽器啟發學習法可能會隨著時間而變更,而且瀏覽器之間可能會不同。 請檢查支援矩陣,並找出最適合您案例的方法。

為了避免讓應用程式進入睡眠狀態,應用程式應該觸發瀏覽器所使用的其中一種啟發學習法。

下列程式碼範例顯示如何使用 Web 鎖定,以讓索引標籤保持喚醒狀態,並避免非預期的連線關閉。

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;
    });
}

針對上述程式碼範例:

  • Web 鎖定是實驗性項目。 條件式檢查會確認瀏覽器支援 Web 鎖定。
  • 會儲存 Promise 解析程式 lockResolver,以在可接受索引標籤進入睡眠狀態時釋放鎖定。
  • 關閉連線時,會呼叫 lockResolver() 來釋放鎖定。 釋放鎖定時,允許索引標籤進入睡眠狀態。

其他資源

作者:Rachel Appel

ASP.NET Core SignalR JavaScript 用戶端程式庫可讓開發人員呼叫伺服器端中樞程式碼。

檢視或下載範例程式碼 \(英文\) (如何下載)

安裝 SignalR 用戶端套件

SignalR JavaScript 用戶端程式庫會以 npm 套件形式傳遞。 下列各節概述不同的用戶端程式庫安裝方式。

使用 npm 安裝

針對 Visual Studio,請在根資料夾中,從 [套件管理員主控台] 執行下列命令。 針對 Visual Studio Code,從 [整合式終端] 執行下列命令。

npm init -y
npm install @microsoft/signalr

npm 會在 node_modules\@microsoft\signalr\dist\browser 資料夾中安裝套件內容。 在 wwwroot\lib 資料夾下方,建立名為 signalr 的新資料夾。 將 signalr.js 檔案複製至 wwwroot\lib\signalr 資料夾。

參考 <script> 元素中的 SignalR JavaScript 用戶端。 例如:

<script src="~/lib/signalr/signalr.js"></script>

使用內容傳遞網路 (CDN)

若要在沒有 npm 必要條件的情況下使用用戶端程式庫,請參考用戶端程式庫的 CDN 裝載複本。 例如:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

在下列 CDN 上,可以提供用戶端程式庫:

使用 LibMan 安裝

LibMan 可以用來安裝 CDN 裝載用戶端程式庫中的特定用戶端程式庫檔案。 例如,只將縮小的 JavaScript 檔案新增至專案。 如需該方式的詳細資料,請參閱新增 SignalR 用戶端程式庫

連線至中樞

下列程式碼會建立並啟動連線。 中樞的名稱不區分大小寫:

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();

跨原始來源連線

瀏覽器一般會從與所要求頁面相同的網域載入連線。 不過,有時候需要連線至另一個網域。

重要

用戶端程式碼必須使用絕對 URL,而不是相對 URL。 將 .withUrl("/chathub") 變更為 .withUrl("https://myappurl/chathub")

若要防止惡意網站從另一個網站讀取敏感性資料,預設會停用跨原始來源連線。 若要允許跨原始來源要求,請在 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");
            });
        }
    }
}

從用戶端呼叫中樞方法

JavaScript 用戶端會透過 HubConnectioninvoke 方法來呼叫中樞上的公用方法。 invoke 方法可接受:

  • 中樞方法的名稱。
  • 中樞方法中所定義的任何引數。

在下列範例中,中樞上的方法名稱為 SendMessage。 傳遞至 invoke 的第二個和第三個引數會對應至中樞方法的 usermessage 引數:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

注意

只有在以預設 SignalR 模式使用 Azure Service 時,才支援從用戶端呼叫中樞方法。 如需詳細資訊,請參閱常見問題集 (azure-signalr GitHub 存放庫)

invoke 方法會傳回 JavaScript Promise。 傳回伺服器上的方法時,會使用傳回值來解析 Promise。 如果伺服器上的方法擲回錯誤,則會拒絕 Promise,並顯示錯誤訊息。 使用 asyncawaitPromisethencatch 方法來處理這些案例。

JavaScript 用戶端也可以透過 HubConnectionsend 方法,以在中樞上呼叫公用方法。 與 invoke 方法不同,send 方法不會等候來自伺服器的回應。 send 方法會傳回 JavaScript Promise。 已將訊息傳送至伺服器時,會解析 Promise。 如果傳送訊息時發生錯誤,則會拒絕 Promise,並顯示錯誤訊息。 使用 asyncawaitPromisethencatch 方法來處理這些案例。

注意

使用 send 不會等到伺服器收到訊息。 因此,無法從伺服器傳回資料或錯誤。

從中樞呼叫用戶端方法

若要從中樞接收訊息,請使用 HubConnectionon 方法來定義方法。

  • JavaScript 用戶端方法的名稱。
  • 中樞傳遞至方法的引數。

在下列範例中,方法名稱為 ReceiveMessage。 引數名稱為 usermessage

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

connection.on 中的上述程式碼會在伺服器端程式碼使用 SendAsync 方法進行呼叫時執行:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR 會比對 SendAsyncconnection.on 中所定義的方法名稱和引數,來判斷要呼叫的用戶端方法。

注意

最佳做法是在 on 後面於 HubConnection 上呼叫 start 方法。 這麼做可確保先在收到任何訊息之前註冊您的處理常式。

錯誤處理和記錄

搭配使用 trycatchasyncawaitPromisecatch 方法,以處理用戶端錯誤。 使用 console.error,以將錯誤輸出至瀏覽器的主控台:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

建立連線時,傳遞要記錄之事件的記錄器和類型,以記錄用戶端記錄追蹤。 會以指定的記錄層級和更高層級來記錄訊息。 可用的記錄層級如下:

  • signalR.LogLevel.Error:錯誤訊息。 僅記錄 Error 訊息。
  • signalR.LogLevel.Warning:潛在錯誤的相關警告訊息。 記錄 WarningError 訊息。
  • signalR.LogLevel.Information:狀態訊息,而且未發生錯誤。 記錄 InformationWarningError 訊息。
  • signalR.LogLevel.Trace:追蹤訊息。 記錄所有項目,包括中樞與用戶端之間傳輸的資料。

HubConnectionBuilder 上使用 configureLogging 方法,以設定記錄層級。 訊息會記錄至瀏覽器主控台:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

重新連線用戶端

自動重新連線

SignalR 的 JavaScript 用戶端可以設定為使用 HubConnectionBuilder 上的 withAutomaticReconnect 方法來自動重新連線。 其預設不會自動重新連線。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

如果不使用任何參數,withAutomaticReconnect() 將用戶端設定為在嘗試每次重新連線之前分別等待 0、2、10 和 30 秒,並在四次嘗試失敗後停止。

開始任何重新連線嘗試之前,HubConnection 將會轉換至 HubConnectionState.Reconnecting 狀態,並引發其 onreconnecting 回呼,而不是在未設定自動重新連線的情況下轉換至 Disconnected 狀態,並觸發其 onclose 回呼 (例如 HubConnection)。 這可讓您向使用者警告已中斷連線,並停用 UI 元素。

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);
});

如果用戶端在前四次嘗試中成功重新連線,則 HubConnection 會轉換回 Connected 狀態,並引發其 onreconnected 回呼。 這可讓您向使用者通知已重新建立連線。

因為該連線對伺服器來說是全新的,所以會將新的 connectionId 提供給 onreconnected 回呼。

警告

如果 HubConnection 已設定為跳過交涉,則將不會定義 onreconnected 回呼的 connectionId 參數。

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() 不會將 HubConnection 設定為重試初始啟動失敗,因此必須手動處理啟動失敗:

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);
    }
};

如果用戶端在前四次嘗試中未成功重新連線,則 HubConnection 將會轉換至 Disconnected 狀態,並引發其 onclose 回呼。 這可讓您向使用者通知已永久中斷連線,並建議重新整理頁面:

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);
});

若要在中斷連線或變更重新連線時間之前設定自訂重新連線嘗試次數,withAutomaticReconnect 接受一個數字陣列,表示在開始每次重新連線嘗試之前要等待的延遲時間 (以毫秒為單位)。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

上述範例會將 HubConnection 設定為在連線遺失後立即開始嘗試重新連線。 這也適用於預設設定。

如果第一次重新連線嘗試失敗,則第二次重新連線嘗試也會立即啟動,而不是像預設設定中那樣等待 2 秒。

如果第二次重新連線嘗試失敗,則第三次重新連線嘗試將會在 10 秒內開始,這又與預設設定類似。

自訂行為接著會與預設行為再次分歧,原因是在第三次重新連線嘗試失敗後停止,而不是在另一個 30 秒內再嘗試一次重新連線嘗試,就像在預設設定中那樣。

如果您想要更充分掌控自動重新連線嘗試的時間和次數,withAutomaticReconnect 接受一個實作 IRetryPolicy 介面的物件,該介面有名為 nextRetryDelayInMilliseconds 的單一方法。

nextRetryDelayInMilliseconds 會採用類型為 RetryContext 的單一引數。 RetryContext 有三個屬性:previousRetryCountelapsedMillisecondsretryReason,分別是 numbernumberError。 在第一次重新連線嘗試之前,previousRetryCountelapsedMilliseconds 都會是零,而 retryReason 會是造成連線中斷的「錯誤」。 每次重試失敗之後,previousRetryCount 將會遞增一次,elapsedMilliseconds 將會更新以反映到目前為止重新連線所花費的時間量 (毫秒),而 retryReason 會是造成上次重新連線嘗試失敗的「錯誤」。

nextRetryDelayInMilliseconds 必須傳回一個數字,代表下一次重新連線嘗試之前所要等待的毫秒數,或者,如果 HubConnection 應該停止重新連線,則傳回 null

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();

或者,您可以撰寫程式碼來手動重新連線用戶端,如手動重新連線中所示範。

手動重新連線

下列程式碼示範一般手動重新連線方式:

  1. 會建立函數 (在此案例中,為 start 函數) 來啟動連線。
  2. 在連線的 onclose 事件處理常式中,呼叫 start 函數。
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

生產實作通常會使用指數輪詢,或重試指定的次數。

瀏覽器睡眠索引標籤

某些瀏覽器具有索引標籤凍結或睡眠功能,以減少非使用中索引標籤的電腦資源使用量。 這可能會導致關閉 SignalR 連線,而且可能會導致不必要的使用者體驗。 瀏覽器會使用啟發學習法來找出索引標籤是否應該進入睡眠狀態,例如:

  • 播放音訊
  • 保留 Web 鎖定
  • 保留 IndexedDB 鎖定
  • 連線至 USB 裝置
  • 擷取視訊或音訊
  • 進行鏡像
  • 擷取視窗或顯示

注意

這些啟發學習法可能會隨著時間而變更,或瀏覽器之間會不同。 請檢查您的支援矩陣,並找出最適合您案例的方法。

為了避免讓應用程式進入睡眠狀態,應用程式應該觸發瀏覽器所使用的其中一種啟發學習法。

下列程式碼範例顯示如何使用 Web 鎖定,以讓索引標籤保持喚醒狀態,並避免非預期的連線關閉。

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;
    });
}

針對上述程式碼範例:

  • Web 鎖定是實驗性項目。 條件式檢查會確認瀏覽器支援 Web 鎖定。
  • 會儲存 Promise 解析程式 (lockResolver),以在可接受索引標籤進入睡眠狀態時釋放鎖定。
  • 關閉連線時,會呼叫 lockResolver() 來釋放鎖定。 釋放鎖定時,允許索引標籤進入睡眠狀態。

其他資源