鴻蒙異步處理從入門到實戰:Promise、async/await、并發池、超時重試全套攻略

在這里插入圖片描述

摘要(介紹目前的背景和現狀)

在鴻蒙(HarmonyOS)里,網絡請求、文件操作、數據庫訪問這類 I/O 都是異步的。主流寫法跟前端類似:Promiseasync/await、回調。想把 app 做得“流暢且不阻塞”,核心在于:合理用 async/await、把錯誤兜住、并發別亂開、遇到慢接口要有“超時/取消/重試”機制。

引言(介紹目前的發展情況和場景應用)

從 API 設計上看,鴻蒙的系統能力(比如 @ohos.net.http@ohos.file.fs@ohos.webSocket 等)都提供了 Promise 風格的方法。我們在實際項目里,通常會封一層“請求工具模塊”,加上統一的超時、重試、攔截日志,然后在頁面中以 async/await 的寫法去調用。本文用一個小 Demo 把這些串起來:頁面里點幾個按鈕,分別觸發請求、并發、取消、文件讀寫等操作,看得到結果和錯誤提示。

異步基礎與風格選型

Promise 鏈式 vs. async/await

  • 鏈式寫法好處是可控的組合then/catch/finally),缺點是可讀性差
  • async/await 寫起來更像同步代碼,可讀性強,但別忘了 try/catch
  • 兩者可以混用:底層工具用 Promise 封裝,業務層面用 async/await 調用。

代碼示例:Promise 鏈式 & async/await 對比

import http from '@ohos.net.http';// 鏈式寫法
function fetchTodoByIdChained(id: number) {const client = http.createHttp();return client.request(`https://jsonplaceholder.typicode.com/todos/${id}`, {method: http.RequestMethod.GET,connectTimeout: 5000,readTimeout: 5000,}).then(res => JSON.parse(res.result as string)).finally(() => client.destroy());
}// async/await 寫法
async function fetchTodoByIdAsync(id: number) {const client = http.createHttp();try {const res = await client.request(`https://jsonplaceholder.typicode.com/todos/${id}`, {method: http.RequestMethod.GET,connectTimeout: 5000,readTimeout: 5000,});return JSON.parse(res.result as string);} finally {client.destroy();}
}

一個可運行的 Demo(頁面 + 工具模塊)

結構建議:
features/async/AsyncDemoPage.ets(頁面)
features/async/asyncKit.ts(工具模塊:超時、取消、重試、并發池、文件讀寫)

工具模塊:asyncKit.ts

// features/async/asyncKit.ts
import http from '@ohos.net.http';
import fs from '@ohos.file.fs';/** 一次 HTTP 請求,帶超時控制 */
export async function httpGet<T = unknown>(url: string, timeoutMs = 6000): Promise<T> {const client = http.createHttp();try {const req = client.request(url, {method: http.RequestMethod.GET,connectTimeout: timeoutMs,readTimeout: timeoutMs,});// 雙保險:用 Promise.race 再做一層超時const res = await Promise.race([req,delayReject(timeoutMs, new Error(`Timeout after ${timeoutMs}ms`))]);const txt = (res as http.HttpResponse).result as string;return JSON.parse(txt) as T;} finally {client.destroy();}
}/** 延遲 resolve/reject */
export function delay(ms: number): Promise<void> {return new Promise(resolve => setTimeout(resolve, ms));
}
export function delayReject<T = never>(ms: number, err: Error): Promise<T> {return new Promise((_, reject) => setTimeout(() => reject(err), ms));
}/** 重試:指數退避 */
export async function withRetry<T>(fn: () => Promise<T>,attempts = 3,baseDelayMs = 300
): Promise<T> {let lastErr: unknown;for (let i = 0; i < attempts; i++) {try {return await fn();} catch (e) {lastErr = e;if (i < attempts - 1) {const backoff = baseDelayMs * Math.pow(2, i); // 300, 600, 1200...await delay(backoff);}}}throw lastErr;
}/** 并發池:限制并發數量,避免把網絡或設備打滿 */
export async function runWithPool<T>(tasks: Array<() => Promise<T>>,concurrency = 3
): Promise<T[]> {const results: T[] = [];let index = 0;const workers: Promise<void>[] = [];async function worker() {while (index < tasks.length) {const cur = index++;try {const r = await tasks[cur]();results[cur] = r;} catch (e) {// 不中斷整體:把錯誤拋出去給上層處理也可results[cur] = e as any;}}}for (let i = 0; i < Math.min(concurrency, tasks.length); i++) {workers.push(worker());}await Promise.all(workers);return results;
}/** 取消控制(簡版):通過外部標記與自定義取消邏輯 */
export class CancelToken {private _cancelled = false;cancel() { this._cancelled = true; }get cancelled() { return this._cancelled; }
}/** 支持“軟取消”的 GET:每步檢查 token,早停 */
export async function httpGetCancellable<T = unknown>(url: string,token: CancelToken,timeoutMs = 6000
): Promise<T> {if (token.cancelled) throw new Error('Cancelled before start');const client = http.createHttp();try {const req = client.request(url, {method: http.RequestMethod.GET,connectTimeout: timeoutMs,readTimeout: timeoutMs,});const res = await Promise.race([req,delayReject(timeoutMs, new Error(`Timeout after ${timeoutMs}ms`))]);if (token.cancelled) throw new Error('Cancelled after response');const txt = (res as http.HttpResponse).result as string;return JSON.parse(txt) as T;} finally {client.destroy();}
}/** 文件讀寫示例(Promise 風格) */
export async function writeTextFile(path: string, content: string): Promise<void> {const file = await fs.open(path, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);try {await fs.write(file.fd, content);} finally {await fs.close(file.fd);}
}export async function readTextFile(path: string): Promise<string> {const file = await fs.open(path, fs.OpenMode.READ_ONLY);try {const stat = await fs.stat(path);const buf = new ArrayBuffer(stat.size);await fs.read(file.fd, buf);return String.fromCharCode(...new Uint8Array(buf));} finally {await fs.close(file.fd);}
}

頁面:AsyncDemoPage.ets

// features/async/AsyncDemoPage.ets
import promptAction from '@ohos.promptAction';
import { httpGet, withRetry, runWithPool, CancelToken, httpGetCancellable, writeTextFile, readTextFile } from './asyncKit';@Entry
@Component
struct AsyncDemoPage {@State log: string = '';token: CancelToken = new CancelToken();private append(msg: string) {this.log = `[${new Date().toLocaleTimeString()}] ${msg}\n` + this.log;}async onFetchOnce() {try {const data = await httpGet<any>('https://jsonplaceholder.typicode.com/todos/1', 4000);this.append(`單次請求成功:${JSON.stringify(data)}`);promptAction.showToast({ message: '請求成功' });} catch (e) {this.append(`單次請求失敗:${(e as Error).message}`);promptAction.showToast({ message: '請求失敗' });}}async onFetchWithRetry() {try {const data = await withRetry(() => httpGet<any>('https://jsonplaceholder.typicode.com/todos/2', 2000), 3, 300);this.append(`重試成功:${JSON.stringify(data)}`);} catch (e) {this.append(`重試最終失敗:${(e as Error).message}`);}}async onParallelLimited() {const ids = Array.from({ length: 8 }, (_, i) => i + 1);const tasks = ids.map(id => () => httpGet<any>(`https://jsonplaceholder.typicode.com/todos/${id}`, 5000));const results = await runWithPool(tasks, 3);const ok = results.filter(r => !(r instanceof Error)).length;this.append(`并發池完成:成功 ${ok}/${results.length}`);}async onCancellable() {this.token = new CancelToken();const p = httpGetCancellable<any>('https://jsonplaceholder.typicode.com/todos/3', this.token, 6000);setTimeout(() => {this.token.cancel();this.append('已發出取消信號');}, 200); // 模擬用戶取消try {const data = await p;this.append(`取消示例返回:${JSON.stringify(data)}`);} catch (e) {this.append(`取消示例結束:${(e as Error).message}`);}}async onFileReadWrite() {try {const path = `/data/storage/el2/base/files/async_demo.txt`;await writeTextFile(path, `hello @ ${Date.now()}`);const text = await readTextFile(path);this.append(`文件讀寫成功:${text}`);} catch (e) {this.append(`文件讀寫失敗:${(e as Error).message}`);}}build() {Column() {Text('異步請求 Demo').fontSize(22).fontWeight(FontWeight.Bold).margin({ bottom: 8 })Row({ space: 8 }) {Button('單次請求').onClick(() => this.onFetchOnce())Button('重試請求').onClick(() => this.onFetchWithRetry())Button('并發池').onClick(() => this.onParallelLimited())}Row({ space: 8 }) {Button('可取消請求').onClick(() => this.onCancellable())Button('文件讀寫').onClick(() => this.onFileReadWrite())}.margin({ top: 8 })Scroll() {Text(this.log).fontSize(14).maxLines(1000)}.margin({ top: 12 }).height('60%').width('100%')}.padding(16).backgroundColor('#FFFFFF').width('100%').height('100%')}
}

說明:

  • 頁面按鈕觸發不同的異步策略:單次請求、帶重試、并發池、軟取消、文件讀寫。
  • promptAction.showToast 做輕提示;日志區能看到詳細過程。
  • “可取消請求”里用自定義 CancelToken 做軟取消,避開直接中斷底層請求可能帶來的不一致。

典型模式與落地細節

超時與取消

代碼示例:超時包裝 + 軟取消要點

  • 超時:Promise.race([真實請求, delayReject(...)])
  • 取消:在關鍵點檢查 token.cancelled,拋錯早停,外層統一善后
// 見 asyncKit.ts -> httpGet / httpGetCancellable

并發與限流

  • Promise.all 一把梭容易把對端打掛;建議并發池控制并發數(如 3~5 個)。
  • 對失敗任務可以部分重試,同時采用 allSettled 匯總成功/失敗。
// 見 asyncKit.ts -> runWithPool

重試與指數退避

  • 固定重試間隔會形成“重試風暴”;指數退避(300ms、600ms、1200ms…)更溫和。
  • 重試只針對冪等操作(GET/查詢),避免對寫操作造成重復副作用。
// 見 asyncKit.ts -> withRetry

應用場景舉例(2~3 個)

場景一:列表頁首屏并發加載(配置 + 資源 + 推薦)

需求:首屏要同時拉 3 個接口,但又不希望對端被瞬時壓垮。
做法:把 3 個任務丟給并發池,限制并發為 2;任一失敗也不要阻塞頁面,可先部分渲染。

import { runWithPool } from './asyncKit';
import { httpGet } from './asyncKit';async function loadHomeFirstScreen() {const tasks = [() => httpGet('/api/config'),() => httpGet('/api/resources'),() => httpGet('/api/recommend'),];const results = await runWithPool(tasks, 2); // 并發 2const [config, resources, recommend] = results;// 容錯:失敗的部分用降級數據或空態return {config: config instanceof Error ? {} : config,resources: resources instanceof Error ? [] : resources,recommend: recommend instanceof Error ? [] : recommend,};
}

要點

  • “先出結果的先渲染”,把首屏時間做好;失敗項做降級。
  • 后續滾動或次要模塊慢慢補齊。

場景二:慢接口 + 超時 + 重試 + 兜底緩存

需求:某接口偶發超時,需要給用戶“盡量穩”的體驗。
做法:請求超時后進行指數退避重試;重試多次失敗,回退到本地緩存。

import { withRetry, httpGet } from './asyncKit';async function fetchUserProfile() {try {const data = await withRetry(() => httpGet('/api/user/profile', 2500),3, // 3 次機會400 // 初始退避);// 成功更新本地緩存await saveCache('user_profile', data);return data;} catch {// 失敗則讀取兜底緩存const cached = await loadCache('user_profile');if (cached) return cached;throw new Error('用戶信息獲取失敗且無緩存');}
}// 這里用文件做個簡單緩存示意
import { writeTextFile, readTextFile } from './asyncKit';
async function saveCache(key: string, data: any) {await writeTextFile(`/data/storage/el2/base/files/${key}.json`, JSON.stringify(data));
}
async function loadCache(key: string) {try {const txt = await readTextFile(`/data/storage/el2/base/files/${key}.json`);return JSON.parse(txt);} catch {return null;}
}

要點

  • 重試要有上限,并且冪等
  • 緩存是兜底,不保證新鮮,但能穩住體驗。

場景三:搜索輸入節流 + 可取消的后端查詢

需求:用戶在搜索框頻繁輸入,我們只想發“最后一次”的請求,之前的要取消。
做法:輸入變化時創建新的 CancelToken,舊的 token 取消;配合小延遲做節流。

import { CancelToken, httpGetCancellable, delay } from './asyncKit';let currentToken: CancelToken | null = null;export async function searchSmart(q: string) {// 簡單節流/防抖:等待 200ms 看輸入是否穩定await delay(200);// 取消上一次if (currentToken) currentToken.cancel();currentToken = new CancelToken();try {const res = await httpGetCancellable(`/api/search?q=${encodeURIComponent(q)}`, currentToken, 5000);return res;} catch (e) {if ((e as Error).message.includes('Cancelled')) {// 被取消視為正常流程,不提示return null;}throw e;}
}

要點

  • 搜索請求往往“多、散、無序”,取消非常關鍵。
  • 被取消不是錯誤,是“正常早停”。

QA 環節

Q1:系統 API 已經有超時參數了,為什么還要 Promise.race 再包一層超時?
A:雙保險。不同網絡階段(DNS、TLS、服務端處理)可能表現不一致,Promise.race 能給你“最外層”的可控時限。實測遇到偶發卡死時,這層很有用。

Q2:為什么不用全局 Promise.all
A:Promise.all 會在第一處 reject 直接短路,且同時放飛所有請求;對于“頁面多個區塊”這類場景,不如“并發池 + allSettled/容錯”的策略穩。

Q3:取消請求有沒有更“硬”的方式?
A:這里示例用的是“軟取消”(業務層檢查 token 自行早停)。某些底層能力不支持直接中斷連接,或者中斷后善后成本高;軟取消更安全、可控。若后續 SDK 提供硬取消且你能正確善后,也可以用。

Q4:重試會不會放大問題?
A:會。一定要限制嘗試次數,并配合指數退避,最好只對 GET/查詢這類冪等操作生效。寫操作(POST/PUT/DELETE)要確認冪等性(比如用冪等鍵)再考慮重試。

Q5:文件路徑為什么放在 /data/storage/el2/base/files/
A:這是應用私有可寫目錄,讀寫權限最穩。實際項目請結合應用沙箱與權限模型,避免寫到不該寫的地方。

總結

鴻蒙里的異步處理,并不玄學:async/await 寫業務Promise 工具層兜底,再配上并發池、超時/取消、重試/退避、緩存兜底,就能把大部分“不可靠的網絡與 I/O”變得可預期。本文的 Demo 給了一個能跑的小框架:

  • 頁面按鈕觸發不同策略,觀察真實效果;
  • 工具模塊沉淀成你項目的“網絡與 I/O 中臺”;
  • 場景案例覆蓋首屏并發、慢接口容錯、搜索可取消。

你可以直接把 asyncKit.ts 抽到公共庫里用,把頁面換成你自己的業務 UI。如果需要,我可以幫你把這些工具函數改造成帶“請求攔截/響應攔截、自動注入 token、統一錯誤碼處理”的完整網絡層模板。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96572.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96572.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96572.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【html2img/pdf 純!純!python將html保存為圖片/pdf!!效果非常的棒!】

素材 a.png html card.html <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>固定樣式卡片</title><style>/* 基礎樣式和頁面居中 */body {font-family: "微軟雅黑", "P…

帶寬評估(三)lossbase_v2

一、優化方向 調整丟包恢復算法的參數:可以通過調整算法中的一些參數,如丟包恢復速率、丟包恢復閾值等,來優化算法的性能。 調整發送窗口大小:在固定丟包場景下,可以通過調整發送窗口大小來控制發送速率,從而減少丟包率。 a=fmtp:96 x-google-min-bitrate=300 二、Goo…

imx6ull-驅動開發篇29——Linux阻塞IO 實驗

目錄 實驗程序編寫 blockio.c blockioApp.c Makefile 文件 運行測試 在之前的文章里&#xff0c;Linux阻塞和非阻塞 IO&#xff08;上&#xff09;&#xff0c;我們學習了Linux應用程序了兩種操作方式&#xff1a;阻塞和非阻塞 IO。 在Linux 中斷實驗中&#xff0c;Linux…

97. 小明逛公園,Floyd 算法,127. 騎士的攻擊,A * 算法

97. 小明逛公園Floyd 算法dijkstra, bellman_ford 是求單個起點到單個終點的最短路徑&#xff0c;dijkstra無法解決負權邊的問題&#xff0c; bellman_ford解決了負權邊的問題&#xff0c;但二者都是基于單起點和單終點。而Floyd 算法旨在解決多個起點到多個終點的最短路徑問題…

?崩壞世界觀中的安全漏洞與哲學映射:從滲透測試視角解構虛擬秩序的脆弱性?

?崩壞世界觀&#xff1a;游戲中的世界&#xff0c;是真實&#xff0c;也是虛幻的&#xff01;對于游戲中的NPC角色而言&#xff0c;TA們生存的世界&#xff0c;是真實的&#xff01;對于游戲玩家而言&#xff0c;游戲中的世界&#xff0c;是虛擬的&#xff01;通過沉浸式的游戲…

【離線安裝】CentOS Linux 7 上離線部署Oracle 19c(已成功安裝2次)

1.部署參考鏈接&#xff1a; CentOS 7 rpm方式離線安裝 Oracle 19chttps://blog.csdn.net/Vampire_1122/article/details/123038137?fromshareblogdetail&sharetypeblogdetail&sharerId123038137&sharereferPC&sharesourceweixin_45806267&sharefromfrom…

小白向:Obsidian(Markdown語法學習)快速入門完全指南:從零開始構建你的第二大腦(免費好用的筆記軟件的知識管理系統)、黑曜石筆記

一、認識Obsidian&#xff1a;不只是筆記軟件的知識管理系統 1.1 什么是Obsidian Obsidian是一個基于本地存儲的知識管理系統&#xff0c;它將你的所有筆記以純文本Markdown格式保存在電腦本地。這個名字來源于黑曜石——一種火山熔巖快速冷卻形成的玻璃質巖石&#xff0c;象…

攻防世界—Confusion1—(模板注入ssti)

一.解題在login和register的頁面中發現這個文件路徑接下去就找有什么點可以利用二.ssti通過題目信息可知是一只蛇把一只大象纏繞起來了&#xff0c;蛇代表python&#xff0c;大象代表php這邊通過python可以推測可能是模板注入&#xff0c;這邊我看其他的解題是說通過看報文信息…

【Protues仿真】基于AT89C52單片機的超聲波測距

目錄 1 HCSR04超聲波測距傳感器 1.1 基本參數 1.2 引腳說明 1.3 工作原理&#xff08;時序圖&#xff09; 2 基于AT89C52單片機的超聲波測距電路原理圖 2.1 硬件連接說明 2.2 工作原理 3 基于AT89C52單片機的超聲波測距控制程序 3.1.1 初始化設置 3.1.2 超聲波測距原…

LLM - Agent核心架構:四大“身體”部件

文章目錄一、Agent核心架構&#xff1a;四大“身體”部件1. 核心大腦&#xff1a;大型語言模型&#xff08;LLM&#xff09;2. 記憶系統&#xff1a;短期與長期記憶3. 工具箱&#xff08;Toolkit&#xff09;&#xff1a;從“思想家”到“行動家”4. 驅動循環&#xff08;Engin…

html-docx-js 導出word

2025.08.23今天我學習了如何將html頁面內容導出到word中&#xff0c;并保持原有格式&#xff0c;效果如下&#xff1a;代碼如下&#xff1a;1&#xff1a;列表頁面按鈕<el-button type"warning" plain icon"el-icon-download" size"mini" cli…

Science Robotics 通過人機交互強化學習進行精確而靈巧的機器人操作

機器人操作仍然是機器人技術中最困難的挑戰之一&#xff0c;其方法范圍從基于經典模型的控制到現代模仿學習。盡管這些方法已經取得了實質性進展&#xff0c;但它們通常需要大量的手動設計&#xff0c;在性能方面存在困難&#xff0c;并且需要大規模數據收集。這些限制阻礙了它…

Dism++備份系統時報錯[句柄無效]的解決方法

當使用Dism進行系統備份時遇到“[句柄無效]”的錯誤&#xff0c;這通常是由于某些文件或目錄的句柄無法正確訪問或已被占用所導致。以下是一種有效的解決方法&#xff1a;一、查看日志文件定位日志文件&#xff1a;首先&#xff0c;打開Dism軟件所在的目錄&#xff0c;并找到其…

華為/思科/H3C/銳捷操作系統操作指南

好的,這是一份針對 華為(VRP)、思科(IOS/IOS-XE)、H3C(Comware)和銳捷(Ruijie OS) 這四大主流網絡設備廠商操作系統的對比操作指南。本指南將聚焦于它們的共性和特性,幫助你快速掌握多廠商設備的基本操作。 四大網絡廠商操作系統綜合操作指南 一、 核心概念與模式對…

一文讀懂 DNS:從域名解析到百度訪問全流程

目錄 前言 一、什么是 DNS&#xff1f;—— 互聯網的 “地址簿” 為什么需要 DNS&#xff1f; DNS 的核心參數 二、DNS 解析原理&#xff1a;遞歸與迭代的協作 1. 兩種核心查詢方式 2. 完整解析流程&#xff08;以www.baidu.com為例&#xff09; 緩存清理命令 三、DNS …

初試Docker Desktop工具

文章目錄1. 概述2. 下載3. 安裝4. 注冊5. 登錄6. 啟動7. 容器8. 運行容器8.1 運行容器的鏡像8.2 獲取示例應用8.3 驗證Dockerfile文件8.4 拉取Alpine精簡鏡像8.5 創建鏡像8.6 運行容器8.7 查看前端9. 訪問靜態資源9.1 本地靜態資源9.2 創建服務器腳本9.3 修改Dockerfile文件9.4…

百度披露Q2財報:營收327億,AI新業務收入首超百億

8月20日&#xff0c;百度發布2025年第二季度財報&#xff0c;顯示季度總營收327億元&#xff0c;百度核心營收263億元&#xff0c;歸屬百度核心凈利潤74億元&#xff0c;同比增長35%。受AI驅動&#xff0c;涵蓋智能云在內的AI新業務收入增長強勁&#xff0c;首次超過100億元&am…

【字母異位分組】

思路 核心思路&#xff1a;使用排序后的字符串作為鍵&#xff0c;將原始字符串分組 鍵的選擇&#xff1a;對于每個字符串&#xff0c;將其排序后得到標準形式作為鍵分組存儲&#xff1a;使用哈希表&#xff0c;鍵是排序后的字符串&#xff0c;值是對應的原始字符串列表結果構建…

高防cdn如何緩存網頁靜態資源

為什么需要優化網頁靜態資源的緩存&#xff1f; 網頁靜態資源包括圖片、CSS、JavaScript等文件&#xff0c;它們通常體積大、訪問頻繁。在網頁訪問過程中&#xff0c;如果每次都從源服務器請求這些靜態資源&#xff0c;會導致網絡延遲和帶寬消耗。而優化網頁靜態資源的緩存&am…

使用Pandas進行缺失值處理和異常值檢測——實戰指南

目錄 一、缺失值處理 1.1 缺失值的識別 1.2 刪除缺失值 1.3 填充缺失值 二、異常值檢測 2.1 異常值的定義 2.2 常用檢測方法 IQR&#xff08;四分位數間距&#xff09;法 Z-score&#xff08;標準分數&#xff09;法 三、實戰案例&#xff1a;基因表達數據預處理 四…