鸿蒙异步处理从入门到实战: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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;基因表达数据预处理 四…