HarmonyOS Game Networking Architecture: Why You Should Not Use fetch Directly

For HarmonyOS game development, using fetch directly may help you get started quickly, but it leads to scattered requests, inconsistent authentication, uncontrolled error handling, and AI streaming workflows that are difficult to implement. This article presents a reusable networking abstraction, centered on three key concepts: HarmonyOS, networking layer, and fetch.

Table of Contents

Technical specification snapshot

Parameter Description
Target platform HarmonyOS / HarmonyOS NEXT
Primary language ArkTS / TypeScript
Communication protocol HTTP / HTTPS / Streaming
Core issue fetch lacks a unified entry point, interception mechanisms, and multi-device strategy control
Recommended architecture HttpClient + API Layer + Interceptor + AIClient
Star count Not provided in the source
Core dependencies fetch, token management module, logging, and retry strategy

Using fetch directly in HarmonyOS games breaks engineering control over networking

In a small demo, fetch is direct and sufficient. But once you move into a real HarmonyOS game project, network requests are no longer just about “getting data.” They become infrastructure that connects login, combat, asset retrieval, user state synchronization, and AI service orchestration.

When pages, components, and business modules all issue requests independently, the system quickly runs into scattered URLs, inconsistent headers, duplicated exception handling, and missing timeout policies. The real problem is not the number of APIs. It is the absence of an abstracted, unified networking layer.

The most common mistake is scattering request logic across pages

// Incorrect example: the page sends requests directly without centralized governance
async function loadBattleData() {
  const res = await fetch('/api/battle/start') // Directly call the battle API
  const data = await res.json() // The page parses the response itself
  return data
}

This pattern works, but it cannot support production requirements such as centralized authentication, telemetry, and retries.

The root reason fetch is not enough in HarmonyOS game projects is the lack of a unified strategy entry point

First, fetch is naturally a low-level request primitive, not a complete production-grade networking layer. It solves “how to send a request,” not “how to govern requests.”

Second, HarmonyOS games often target multiple device types at the same time, including phones, tablets, and TVs. Different devices have different network conditions, interaction rhythms, and timeout tolerances. Without a unified request control layer, you cannot enforce device-specific strategies.

Without a unified entry point, observability and governance degrade immediately

// Example of distributed calls
await fetch('/api/list')
await fetch('/api/user')
await fetch('/api/story')

With this structure, it becomes difficult to standardize request logs, latency metrics, error classification, and trace correlation.

Without interceptors, token handling and error processing become increasingly chaotic

// Problem example: every call site assembles its own auth header
await fetch('/api/user', {
  headers: {
    Authorization: getToken() // Token logic is scattered across the business layer
  }
})

Once the token refresh mechanism changes, the required updates will spread across the entire project.

The correct approach is to abstract networking as an extensible system capability

A more reliable solution is to define a four-layer structure: HttpClient provides a unified request entry point, the API Layer encapsulates business semantics, Interceptor handles cross-cutting concerns, and AIClient provides AI-specific request capabilities.

That means pages no longer care about URLs, headers, or error translation. They only consume explicit business methods such as getUser(), startBattle(), and chat().

A minimal viable HttpClient should provide one unified request entry and one unified error exit

class HttpClient {
  async request(url: string, options: RequestInit = {}) {
    try {
      const finalOptions = this.withCommonHeaders(options) // Inject common headers
      const response = await fetch(url, finalOptions) // Send the unified request
      const result = await response.json() // Parse the JSON response uniformly

      if (!response.ok) {
        throw new Error(result?.message || 'Request failed') // Throw business errors consistently
      }

      return result
    } catch (error) {
      this.handleError(error) // Route all errors through one handler
      throw error
    }
  }

  private withCommonHeaders(options: RequestInit): RequestInit {
    return {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        Authorization: getToken(), // Attach the token automatically
        ...(options.headers || {})
      }
    }
  }

  private handleError(error: unknown) {
    console.error('[NetworkError]', error) // Output logs consistently
  }
}

export const http = new HttpClient()

This code centralizes request dispatching, response parsing, token injection, and error handling into a single entry point.

The API layer should carry business semantics instead of continuing to expose URLs

After introducing HttpClient, the next step is not to keep calling request from pages. Instead, build an API layer that converts endpoints into business actions. This reduces page coupling and minimizes the impact of future API changes.

export const UserAPI = {
  getProfile() {
    return http.request('/api/user/profile') // User profile endpoint
  }
}

export const GameAPI = {
  startBattle() {
    return http.request('/api/battle/start', {
      method: 'POST' // Express the business action explicitly
    })
  }
}

This refactors “pages accessing URLs” into “pages invoking business capabilities.”

The page layer should depend only on business interfaces, not low-level request details

const profile = await UserAPI.getProfile() // The page consumes only business methods
const battle = await GameAPI.startBattle() // The page does not care about URLs or headers

This is how you cleanly separate UI, business flow, and networking implementation.

AI requests must be designed independently from the standard API layer

AI workloads differ significantly from standard REST APIs. They may require long-lived connections, streaming output, context stitching, chunked callbacks, and longer timeout windows. If AI requests are mixed directly into standard business traffic, the design will almost certainly become unmanageable over time.

Insert image description here AI Visual Insight: This image functions more like a thematic illustration and visual entry point than a runtime code screenshot. Its purpose is to establish a visual anchor for the upcoming discussion on structured networking architecture in HarmonyOS development.

AIClient should encapsulate both standard chat requests and streaming responses independently

class AIClient {
  async chat(message: string) {
    return http.request('/ai/chat', {
      method: 'POST',
      body: JSON.stringify({ message }) // Encapsulate the message body uniformly
    })
  }

  async streamChat(message: string, onData: (chunk: string) => void) {
    const response = await fetch('/ai/chat/stream', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: getToken() // Streaming requests also require authentication
      },
      body: JSON.stringify({ message })
    })

    // Pseudocode: read the streaming response chunk by chunk
    onData('stream chunk') // Callback for incremental content
    return response
  }
}

The key idea behind this abstraction is to isolate AI services from standard business APIs so that protocol behavior and timeout strategies do not interfere with each other.

Multi-device adaptation requires the networking layer to adjust strategies dynamically by device and network condition

HarmonyOS emphasizes a unified cross-device experience, but a unified experience does not mean identical parameters. TVs are more likely to operate in weak-network environments, tablets may handle heavier asset downloads, and phones are more sensitive to interaction latency.

For that reason, the networking layer should support dynamic adjustment of timeouts, retry counts, and concurrency strategies based on device type, network quality, and API priority.

Dynamically configuring request strategies by device is a key step toward production readiness

function getTimeoutByDevice(deviceType: string): number {
  if (deviceType === 'tv') {
    return 5000 // TVs are more likely to face weak networks, so allow a longer timeout
  }

  if (deviceType === 'tablet') {
    return 4000 // Tablets use a middle-ground value
  }

  return 3000 // Phones prioritize interaction responsiveness
}

This logic demonstrates how the networking layer can apply differentiated governance across devices.

A maintainable HarmonyOS game networking architecture must cover both standard requests and AI requests

The recommended structure is as follows: the page layer calls only the API layer or AIService; the API layer calls HttpClient; HttpClient passes through interceptors before reaching the actual network module. AI requests are handled independently by AIClient.

// Recommended data flow
// Page -> API -> HttpClient -> Interceptor -> Network
// AIService -> AIClient -> Stream/HTTP Network

The value of this structure is clear: standard business flows and AI workflows remain cleanly separated, cross-cutting capabilities are reusable, and you can replace the underlying implementation later without rewriting every page.

The most common misconception is not whether you can use fetch, but whether fetch is a complete architecture

The most common mistakes include sending requests directly from pages, having no API layer, distributing error handling everywhere, mixing AI endpoints with standard APIs, and lacking retry or timeout strategies. At their core, all of these issues point to the same problem: missing system design.

In HarmonyOS games, networking is not a scattered utility. It is infrastructure that supports login, combat, synchronization, and AI interaction. fetch can serve as the low-level implementation, but it should never replace the networking architecture itself.

FAQ

FAQ 1: Is fetch completely unusable in HarmonyOS game projects?

No. fetch can absolutely be used as the low-level primitive, but it should not be exposed directly to the page layer. The correct approach is to encapsulate it inside HttpClient or a higher-level abstraction.

FAQ 2: Why should AI requests be separated into a dedicated AIClient?

Because AI requests usually involve streaming output, long timeouts, context management, and specialized error handling. They differ significantly from standard REST requests, and mixing them together quickly increases complexity.

FAQ 3: When do you need to introduce interceptors, retries, and multi-device strategies?

As soon as your project includes login authentication, unified error codes, weak-network scenarios, multi-device adaptation, or AI integration requirements, you should introduce them early. These are not optional optimizations. They are the foundation of runtime stability.

Core summary

This article explains, from the perspective of HarmonyOS games and AI scenarios, why using fetch directly falls short in areas such as unified request entry, interceptors, retries, token management, multi-device adaptation, and streaming AI requests. It also presents a production-ready abstraction based on HttpClient, an API layer, interceptors, and AIClient.