HarmonyOS —— ArkTS 裏優雅取消網絡請求(RemoteCommunicationKit 實戰筆記)

鴻蒙開發者第四期活動

前面一篇我已經寫過一篇《HarmonyOS —— 發送網絡請求(ArkTS)實戰筆記》,主要是圍繞:rcp.createSession() + session.get/post/fetch... 怎麼發請求。 但光“會發請求”還不夠,在真實項目裏,還有一個同樣重要的能力:

👉 怎麼“及時叫停”那些不該再繼續的網絡請求?

比如這些場景你肯定很熟悉:

  • 用户在搜索框裏瘋狂改關鍵字,前一個請求還沒回來,新的請求已經發出去了。
  • 頁面已經被用户關掉 / 跳轉了,老頁面發出的網絡請求還在後台“孤獨地奔跑”。
  • 大文件上傳時,用户點了“取消”,你總得真·停下這次上傳吧。

這時候,“取消網絡請求” 就是必備能力。RemoteCommunicationKit 也給了我們一套很順手的 API:session.cancel(...)


1. 能在哪些設備上用?先看約束

和發送網絡請求一樣,取消網絡請求能力支持的設備包括:

  • Phone
  • 2in1
  • Tablet
  • Wearable

並且從 5.1.1(19) 開始,TV 設備也一起支持了。

也就是説:

只要你的設備能用 rcp 發請求,基本也就能用 session.cancel(...) 來取消請求。


2. API 認人:session.cancel(requestToCancel?: Request | Request[])

核心只有這一個方法:

cancel(requestToCancel?: Request | Request[]): void

它有兩種完全不同的用法:

  1. 取消指定請求
  • 傳入單個 Request
session.cancel(request1);
  • 傳入 Request[] 數組:
session.cancel([request1, request2]);
  1. 取消所有正在進行的請求
  • 不傳任何參數:
session.cancel();

官方也説得很明確:

  • 不傳參 → 取消當前 session全部網絡請求
  • 傳入一個或一組 Request → 只取消這些對應的請求。

這個設計我還挺喜歡的:

同一個會話下的所有請求,都可以被“一鍵清空”, 又支持對某一個/一批請求做“精準打擊”。


3. 單獨取消某個網絡請求:綁定 Request 對象

來看官方示例,順便加上一點自己的理解。

3.1 示例代碼(官方 + 我的註釋)

import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 1. 創建會話
const session = rcp.createSession();

// 2. 創建 request1、request2(注意:這裏要保留這兩個 Request 的引用)
let request1 = new rcp.Request("https://www.example.com");
let request2 = new rcp.Request("https://www.example.com");

// 3. 分別發起請求
session.fetch(request1).then((response: rcp.Response) => {
  console.info(`The response1 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
  console.error(`Request1 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
});

session.fetch(request2).then((response: rcp.Response) => {
  console.info(`The response2 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
  console.error(`Request2 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
});

// 4. 單獨取消 request1、request2
session.cancel(request1);
session.cancel(request2);

3.2 要點拆解

  1. 取消是基於 Request 實例匹配的
  • 也就是説,session.cancel(request1) 能找到並取消的,是“用這個 request1 發出去的請求”。
  • 所以:你必須保留 Request 對象的引用,才能在之後取消它。
  1. fetch / get 等返回的 Promise 會如何?
  • 被取消的請求,通常會走到 .catch(...) 分支(err.code 對應一個“被取消”的錯誤碼,具體可以按後端/文檔查)。
  • 這意味着:在 UI 裏,你可以通過錯誤碼識別“是用户主動取消”還是“網絡真出錯”。
  1. 適合用在哪些場景?
  • 搜索框實時聯想: 每次用户輸入新關鍵字,就取消上一個掛起的請求,只保留“最新的一次”。
  • 某個 Tab 頁內部: Tab A 發了一個請求,用户切到 Tab B,就只取消 A 對應的請求,而不是把整個會話全殺掉。
  • 單個操作帶來的請求: 比如一個大文件上傳,只為它顯示一個“取消”按鈕,對應的就是隻 cancel 這一條。

3.3 一個實戰小寫法:保存 Request → 做取消按鈕

比如我們有一個“立即搜索”按鈕和“取消請求”按鈕,可以這麼寫(偽代碼):

let session = rcp.createSession();
let currentRequest: rcp.Request | undefined = undefined;

async function search(keyword: string) {
  // 每次搜索前先取消上一次掛起的請求
  if (currentRequest) {
    session.cancel(currentRequest);
    currentRequest = undefined;
  }

  // 創建新的 Request
  const url = `https://api.example.com/search?q=${encodeURIComponent(keyword)}`;
  const req = new rcp.Request(url, 'GET');
  currentRequest = req;

  session.fetch(req)
    .then((resp: rcp.Response) => {
      console.info(`search response: ${resp.statusCode}`);
      // TODO: 解析響應並更新 UI
    })
    .catch((err: BusinessError) => {
      console.error(`search err: ${err.code}, message: ${JSON.stringify(err)}`);
      // 可以根據 err.code 判斷是不是被 cancel 了
    });
}

function cancelSearch() {
  if (currentRequest) {
    session.cancel(currentRequest);
    currentRequest = undefined;
  }
}

這就是最典型的“只保留最新請求”的寫法。


4. 一鍵取消全部請求:session.cancel()

另一個極常用的場景就是:頁面銷燬 / 路由跳轉的時候,直接把當前會話裏的所有請求全撤

4.1 官方示例

import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 1. 創建會話
const session = rcp.createSession();

// 2. 創建 request1、request2
let request1 = new rcp.Request("https://www.example.com");
let request2 = new rcp.Request("https://www.example.com");

// 3. 分別發起請求
session.fetch(request1).then((response: rcp.Response) => {
  console.info(`The response1 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
  console.error(`Request1 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
});

session.fetch(request2).then((response: rcp.Response) => {
  console.info(`The response2 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
  console.error(`Request2 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
});

// 4. 取消全部 request
session.cancel();

4.2 我會怎麼用?

① 頁面生命週期裏統一 cancel
  • 在頁面的 aboutToDisappear / onPageHide / onDestroy 之類的生命週期裏: 直接來一句:
session.cancel();
  • 好處:
  • 避免“頁面已經沒了,舊請求回來還在強行更新 UI”這種詭異問題;
  • 避免無意義的流量消耗。
② 全局 loading 場景

比如你做了一個“全局加載中 + 取消按鈕”:

  • 有多個接口一起請求(比如併發加載用户信息、消息列表、配置項),
  • 用户一旦點“取消加載”,你就可以直接:
session.cancel();  // 全部停下

如果想更精細一點,可以配合 多個 session 分組管理

  • 一個 session 專門管“用户中心”請求;
  • 一個 session 管“當前頁面數據”;
  • 某個模塊銷燬時,只 cancel 對應 session 的請求。

5. 一些實戰小建議 & 踩坑記錄

最後,總結一下我自己在看這個能力時想到的一些實踐點,算是以後寫代碼時給自己的提醒:

5.1 Request 引用一定要保留好

  • session.cancel(request) 是靠 Request 實例 匹配的, 所以你必須把這個 Request 保存下來(比如放在 @State / class 成員 / 閉包裏)。
  • 如果你只是用 session.get(url) 這種快捷方法,而沒有顯式構造 Request 對象,那就不方便做“針對某一條請求的精準取消”。

想精準取消,推薦直接用 new rcp.Request(...) + session.fetch(...) 這一套。

5.2 cancel 之後 Promise 一般會走 catch

  • 被取消的請求,在回調上通常表現為 .catch(err)
  • 記得在 .catch 裏區分:
  • 真正的網絡錯誤(超時、DNS 失敗、服務器 5xx 等)
  • 用户主動取消(可通過 err.code 判斷)

UI 層就可以做到:

  • 主動取消 → 不彈“請求失敗” toast,只是靜默結束;
  • 真錯誤 → 彈提示、埋點等。

5.3 和 session.close() 的配合

如果你有一段流程是:

  1. 創建 session
  2. 發一堆請求
  3. 某個時機你決定全部中斷並清理資源

推薦做法:

session.cancel();  // 先取消所有掛起請求
session.close();   // 再關閉會話

這樣可以避免有“半截請求”卡着不結束的情況。

5.4 複雜頁面:建議為每個功能域獨立一個 session

比如一個複雜的頁面有:

  • 頂部 Banner 加載
  • 中間列表加載
  • 底部推薦加載

你可以:

  • 用一個 session 管這個頁面;
  • 頁面銷燬時 session.cancel() 一把梭;
  • 如果還有全局層面的會話(比如 WebSocket 重用的),則分開管理,避免誤殺。

6. 小結:一句話再 recap 一次

一句話再記一遍: 在 HarmonyOS ArkTS 裏,用 rcp.createSession() 發請求之後, 👉 想“精準取消某個請求”,就把對應的 Request 傳給 session.cancel(request); 👉 想“一鍵取消當前會話的所有請求”,就直接調用 session.cancel() 不帶參數。