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
它有兩種完全不同的用法:
- 取消指定請求
- 傳入單個
Request:
session.cancel(request1);
- 傳入
Request[]數組:
session.cancel([request1, request2]);
- 取消所有正在進行的請求
- 不傳任何參數:
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 要點拆解
- 取消是基於 Request 實例匹配的
- 也就是説,
session.cancel(request1)能找到並取消的,是“用這個request1發出去的請求”。 - 所以:你必須保留 Request 對象的引用,才能在之後取消它。
fetch/get等返回的 Promise 會如何?
- 被取消的請求,通常會走到
.catch(...)分支(err.code對應一個“被取消”的錯誤碼,具體可以按後端/文檔查)。 - 這意味着:在 UI 裏,你可以通過錯誤碼識別“是用户主動取消”還是“網絡真出錯”。
- 適合用在哪些場景?
- 搜索框實時聯想: 每次用户輸入新關鍵字,就取消上一個掛起的請求,只保留“最新的一次”。
- 某個 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() 的配合
如果你有一段流程是:
- 創建
session - 發一堆請求
- 某個時機你決定全部中斷並清理資源
推薦做法:
session.cancel(); // 先取消所有掛起請求
session.close(); // 再關閉會話
這樣可以避免有“半截請求”卡着不結束的情況。
5.4 複雜頁面:建議為每個功能域獨立一個 session
比如一個複雜的頁面有:
- 頂部 Banner 加載
- 中間列表加載
- 底部推薦加載
你可以:
- 用一個 session 管這個頁面;
- 頁面銷燬時
session.cancel()一把梭; - 如果還有全局層面的會話(比如 WebSocket 重用的),則分開管理,避免誤殺。
6. 小結:一句話再 recap 一次
一句話再記一遍: 在 HarmonyOS ArkTS 裏,用
rcp.createSession()發請求之後, 👉 想“精準取消某個請求”,就把對應的Request傳給session.cancel(request); 👉 想“一鍵取消當前會話的所有請求”,就直接調用session.cancel()不帶參數。