跳到主要内容

第九章:网络请求

本章配套代码:code/Chapter9(HTTP GET 请求 Demo) 前置要求:已完成数据持久化章节

9.1 学习目标

  • 掌握 http 模块发送 GET/POST 请求
  • 掌握 JSON 数据解析
  • 理解超时、错误处理与重试
  • 学会封装请求工具类

9.2 网络权限

发起网络请求前,必须在 module.json5 声明 INTERNET 权限

{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}

⚠️ 忘记加权限是最常见的网络请求报错原因(报错 201 无权限)。

9.3 HTTP GET 请求

import { http } from '@kit.NetworkKit'

async function fetchData(): Promise<string> {
const httpRequest = http.createHttp() // ① 创建请求对象

try {
const response = await httpRequest.request( // ② 发起请求
'https://v1.jinrishici.com/all.json',
{
method: http.RequestMethod.GET,
header: {
'Content-Type': 'application/json'
},
expectDataType: http.HttpDataType.STRING,
connectTimeout: 10000, // 连接超时(ms)
readTimeout: 10000 // 读取超时(ms)
}
)

if (response.responseCode === 200) { // ③ 检查状态码
return response.result as string
}
throw new Error(`HTTP Error: ${response.responseCode}`)
} finally {
httpRequest.destroy() // ④ 必须销毁,释放资源
}
}

请求流程:

createHttp() → request() → 检查 responseCode → 解析 result → destroy()
① ② ③ ④ ⑤

🔑 destroy() 必须调用(finally 中),否则连接泄漏。

💡 这里使用公开接口「今日诗词」:每次 GET 都会返回一句随机古诗词(字段 content 诗句、origin 出处、author 作者、category 分类),把网络请求和诗词主题结合起来,文艺又实用。

9.4 POST 请求

async function postData(data: object): Promise<boolean> {
const httpRequest = http.createHttp()

try {
const response = await httpRequest.request(
'https://api.example.com/data',
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json'
},
extraData: JSON.stringify(data) // 请求体(JSON 字符串)
}
)
return response.responseCode === 200 || response.responseCode === 201
} finally {
httpRequest.destroy()
}
}

💡 POST 没有合适的公开诗词示例接口,api.example.com 为示意地址,实际开发时替换为后端接口即可。

其他方法:

// PUT(更新)
method: http.RequestMethod.PUT

// DELETE(删除)
method: http.RequestMethod.DELETE

// 请求头
header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer xxx' }

9.5 数据解析

9.5.1 JSON 解析

// 定义用户数据类型与响应模型
interface User {
id: number
name: string
email: string
}

interface ApiResponse<T> {
code: number
message: string
data: T
}

// 解析
const result = JSON.parse(response.result as string) as ApiResponse<User[]>
if (result.code === 0) {
console.log(result.data) // 泛型推导出类型安全的数据
}

💡 用泛型接口 + as 断言,让 JSON 数据获得类型安全。

9.5.2 封装请求工具类

import { http } from '@kit.NetworkKit'

interface ApiResponse<T> {
code: number
message: string
data: T
}

class HttpUtil {
private static instance: HttpUtil
private baseUrl: string = 'https://api.example.com'

static getInstance(): HttpUtil {
if (!HttpUtil.instance) {
HttpUtil.instance = new HttpUtil()
}
return HttpUtil.instance
}

async get<T>(path: string): Promise<T> {
const httpRequest = http.createHttp()
try {
const response = await httpRequest.request(
`${this.baseUrl}${path}`,
{
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' }
}
)
const result = JSON.parse(response.result as string) as ApiResponse<T>
if (result.code === 0) {
return result.data
}
throw new Error(result.message)
} finally {
httpRequest.destroy()
}
}

async post<T>(path: string, data: object): Promise<T> {
const httpRequest = http.createHttp()
try {
const response = await httpRequest.request(
`${this.baseUrl}${path}`,
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(data)
}
)
const result = JSON.parse(response.result as string) as ApiResponse<T>
if (result.code === 0) {
return result.data
}
throw new Error(result.message)
} finally {
httpRequest.destroy()
}
}
}

export default HttpUtil

9.6 页面中的使用(Loading/Error 三态)

网络请求页面应处理 加载中 / 失败 / 成功 三种状态:

@Entry
@ComponentV2
struct UserListPage {
@Local users: User[] = []
@Local isLoading: boolean = false
@Local error: string = ''

async aboutToAppear(): Promise<void> {
await this.loadUsers()
}

async loadUsers() {
this.isLoading = true
this.error = ''

try {
this.users = await HttpUtil.getInstance().get<User[]>('/users')
} catch (e) {
this.error = (e as Error).message
} finally {
this.isLoading = false
}
}

build() {
Column({ space: 16 }) {
Text('用户列表').fontSize(24)

if (this.isLoading) {
LoadingProgress().width(40).height(40) // 加载中
} else if (this.error) {
Column() {
Text(this.error).fontColor(Color.Red) // 失败
Button('重试').onClick(() => this.loadUsers())
}
} else {
List({ space: 8 }) { // 成功
ForEach(this.users, (user: User) => {
ListItem() { /* 用户卡片 */ }
})
}
}
}
.padding(16)
}
}

三态处理范式:

isLoading ? LoadingProgress
: error ? (错误提示 + 重试按钮)
: (数据列表)

9.7 错误处理与重试

9.7.1 超时与网络异常

try {
const response = await httpRequest.request(url, {
connectTimeout: 10000, // 连接超时
readTimeout: 10000 // 读取超时
})
} catch (e) {
// 网络不可达、超时、解析失败等
console.error((e as BusinessError).message)
}

9.7.2 自动重试

async function requestWithRetry<T>(path: string): Promise<T> {
let retryCount = 0
const maxRetries = 3

while (retryCount < maxRetries) {
try {
return await HttpUtil.getInstance().get<T>(path)
} catch (error) {
retryCount++
if (retryCount >= maxRetries) {
throw error
}
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)) // 递增退避
}
}
throw new Error('Max retries exceeded')
}

9.8 综合示例

对应 code/Chapter9Index.ets,使用公开 API「今日诗词」演示真实请求,每次请求随机返回一句古诗词:

import { http } from '@kit.NetworkKit'

// 今日诗词接口返回的数据结构
interface PoemResult {
content: string // 诗句
origin: string // 出处(诗名/词牌名)
author: string // 作者
category: string // 分类
}

@Entry
@ComponentV2
struct Index {
@Local responseData: string = '点击按钮,随机收获一句古诗词'
@Local isLoading: boolean = false
@Local statusMessage: string = ''

async fetchData() {
this.isLoading = true
this.statusMessage = '请求中...'
const httpRequest = http.createHttp()

try {
const response = await httpRequest.request(
'https://v1.jinrishici.com/all.json',
{
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
expectDataType: http.HttpDataType.STRING,
connectTimeout: 10000,
readTimeout: 10000
}
)

if (response.responseCode === 200) {
const poem = JSON.parse(response.result as string) as PoemResult
this.responseData = `${poem.content}」\n—— ${poem.author}${poem.origin}》\n分类:${poem.category}`
this.statusMessage = `请求成功 (${response.responseCode})`
} else {
this.statusMessage = `请求失败 (${response.responseCode})`
}
} catch (e) {
this.statusMessage = `异常: ${(e as Error).message}`
} finally {
httpRequest.destroy()
this.isLoading = false
}
}

build() {
// 按钮 + 状态 + LoadingProgress + 响应展示(白色卡片)
}
}

运行效果:

  • 点击 "GET 请求" 发起真实网络请求
  • 加载中显示 LoadingProgress,状态文字提示"请求中..."
  • 成功后状态变绿,展示一句随机古诗词(含诗句、作者、出处、分类)
  • 失败时状态显示错误码/异常信息

运行效果截图:

Chapter9 网络请求总览

请求成功返回诗词卡片

今日诗词完整界面

🔧 提示:模拟器可访问外网。若失败请检查:INTERNET 权限、网络连接、今日诗词 API 可达性。

9.9 常见问题

Q:请求报错 201? 没有 INTERNET 权限。检查 module.json5requestPermissions

Q:如何设置超时? request() 的 options 中设 connectTimeoutreadTimeout

Q:如何实现文件上传? POST + extraData 传文件流,或使用 @ohos.request 上传任务。

Q:response.result 如何解析? 根据 expectDataType 转换。字符串 JSON 用 JSON.parse

9.10 本章小结

知识点说明
INTERNET 权限必备前置条件
GET/POSThttp.createHttp() + request()
JSON 解析泛型 + as 断言
三态处理加载中/失败/成功
超时与重试connectTimeout + 退避重试
工具类封装单例 + 泛型

9.11 课后练习

  1. 实现天气查询:请求公开天气 API 并展示城市/温度/湿度
  2. 设计带分页的新闻列表(触底请求下一页)
  3. 实现登录功能:POST 用户名密码,校验 token
  4. 封装带超时与重试的 HttpUtil
  5. 将练习整合进 Chapter9/Index.ets

9.12 参考资料

评论

加载中…
加载中...