前言

HTTP 请求写通不难,难的是把请求前、请求中、请求失败和请求结束都写完整。很多示例只演示 request() 成功后怎么拿数据,但真实页面更容易出问题的地方,是重复点击、非 200 状态、异常和资源释放。

HarmonyOS7 使用 http.createHttp() 创建请求对象后,用完要记得 destroy()。我一般会把它放进 finally网络代码不要只看成功回调,释放资源才是长期稳定的底线。

为什么网络请求最容易写成只顾眼前能跑

因为成功路径最好写。接口通了、数据出来了、列表显示了,很多人就会自然觉得这段请求逻辑已经算完成了。

为文章中的“一个请求页应该有哪些状态”绘制一张 sketch-notes 风格的流程图。请用手绘流程

但真实项目里,真正拖后腿的往往不是成功路径,而是那些顺手被省掉的小地方:重复点击怎么办,状态码异常怎么办,抛错之后怎么兜底,请求对象最后有没有释放。

所以这篇文章真正想强调的,不是“怎么把请求发出去”,而是“怎么把一次请求完整收尾”。

一个请求页应该有哪些状态

以用户列表为例,最少要准备这些状态。

为文章“先把请求页真正要兜住的事情想清楚”绘制一张 sketch-notes 风格的框架图。画面要像

状态 作用
loading 防止重复点击,控制按钮文案
errorText 展示失败原因
users 渲染成功后的列表
responseCode 判断 区分有响应和业务成功

拿到响应不等于请求成功。接口返回 404、500,也是有响应,但页面不能当成正常数据处理。

先把请求页真正要兜住的事情想清楚

请求页最怕的不是接口一时没通,而是页面没有把这次请求前后该做的事兜住。比如按钮点下去之后要不要立刻禁用,失败后页面要不要留下可见提示,旧数据是继续保留还是清空,请求结束后资源有没有正常释放。

你先把这些问题想清楚,再看 loadingerrorTextusers 这些状态,就不会只把它们当成“语法上要声明的变量”。它们分别对应的是交互保护、失败反馈和成功结果,职责一旦分清,后面接真实接口时也更不容易乱。

完整 ArkTS 示例

import { http } from '@kit.NetworkKit'

interface UserInfo {
  id: number
  name: string
  role: string
}

@Entry
@Component
struct HttpDestroyPage {
  @State loading: boolean = false
  @State errorText: string = ''
  @State users: UserInfo[] = []

  async loadUsers(): Promise<void> {
    if (this.loading) {
      return
    }

    this.loading = true
    this.errorText = ''
    const request = http.createHttp()

    try {
      const result = await request.request('https://example.com/api/users', {
        method: http.RequestMethod.GET,
        connectTimeout: 6000,
        readTimeout: 6000
      })

      if (result.responseCode !== 200) {
        this.errorText = `请求失败:${result.responseCode}`
        return
      }

      this.users = [
        { id: 1, name: '林一', role: '产品经理' },
        { id: 2, name: '陈晨', role: 'HarmonyOS 开发' }
      ]
    } catch (err) {
      this.errorText = '网络异常,请稍后重试'
    } finally {
      request.destroy()
      this.loading = false
    }
  }

  build() {
    Column({ space: 12 }) {
      Row() {
        Text('用户列表')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
        Blank()
        Button(this.loading ? '加载中' : '刷新')
          .height(34)
          .enabled(!this.loading)
          .onClick(() => {
            this.loadUsers()
          })
      }
      .width('100%')

      if (this.errorText.length > 0) {
        Text(this.errorText)
          .fontSize(13)
          .fontColor('#D32F2F')
          .padding(12)
          .backgroundColor('#FFF0F0')
          .borderRadius(8)
          .width('100%')
      }

      List({ space: 8 }) {
        ForEach(this.users, (item: UserInfo) => {
          ListItem() {
            Row() {
              Text(item.name.substring(0, 1))
                .fontSize(16)
                .fontColor(Color.White)
                .width(38)
                .height(38)
                .textAlign(TextAlign.Center)
                .backgroundColor('#1E88E5')
                .borderRadius(19)

              Column({ space: 4 }) {
                Text(item.name)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                Text(item.role)
                  .fontSize(12)
                  .fontColor('#777777')
              }
              .alignItems(HorizontalAlign.Start)
              .margin({ left: 10 })
            }
            .padding(14)
            .backgroundColor(Color.White)
            .borderRadius(10)
          }
        }, (item: UserInfo) => item.id.toString())
      }
      .layoutWeight(1)
    }
    .padding(16)
    .backgroundColor('#F5F7FA')
    .height('100%')
  }
}

把关键代码一段段拆开

if (this.loading) return 是第一道保护。用户连续点刷新时,不会同时发起多次请求,列表状态也不容易被后返回的旧请求覆盖。

responseCode 要单独判断。await request.request() 成功返回,只说明拿到了响应,不代表接口业务成功。

request.destroy() 放在 finally 里。无论成功、失败、状态码异常,都会执行释放。这个位置比写在 try 末尾稳得多,因为 try 中途 return 或抛错时也不会漏掉。

示例里用本地数组模拟解析结果。真实项目中可以把 result.result 解析成 UserInfo[],但解析失败也应该进入错误处理,不要让页面静默空白。

页面销毁和请求收尾要一起考虑

这篇示例里,请求对象是在 loadUsers() 里临时创建的,所以放进 finallydestroy() 就够了,思路也最直观:这一轮请求不管走到哪里,最后都要把自己收干净。

如果你后面做的是上传、大文件下载、轮询或者一个页面里并发多个请求,就不能只停在这一步了。那时要继续考虑页面离开时怎么取消未完成请求、多个请求对象怎么统一管理、旧请求返回后会不会覆盖新状态。

请求失败时也不要只打印日志。日志是给开发者看的,页面提示和重试入口才是给用户看的。

新手最容易踩的坑

这一类示例最容易让人产生错觉:界面出来了,就以为已经掌握了。其实真正容易出问题的地方,通常都在效果之外,比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。

所以你练这篇内容时,别只看“现在能不能跑”,还要继续看“以后好不好改”。能把这个习惯养起来,你写出来的页面会比单纯照着示例拼出来的页面稳很多。

放进真实项目还要补什么

示例代码的重点是把核心思路讲明白,所以很多工程化细节会故意省掉。真正落到项目里时,你通常还要继续补接口联动、异常处理、边界保护、资源抽离,以及和其他页面状态之间的配合。

比较稳的做法是分三步走:先把结构和职责立住,再把真实业务接进去,最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”,而是真的更接近可以长期维护的业务代码。

写在最后

HTTP 示例多写几行不是啰嗦,而是把真实项目里最容易漏的部分补上:防重复、判状态、给错误、释放资源。destroy() 这一步很小,但它决定网络请求代码能不能长期稳定。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐