Skip to content

Network Requests

Network requests are essential for mini programs to communicate with backend services. This guide covers HTTP requests, WebSocket connections, file uploads/downloads, and best practices.

Basic HTTP Requests

Making GET Requests

javascript
// Simple GET request
wx.request({
  url: 'https://api.example.com/users',
  method: 'GET',
  success: (res) => {
    console.log('Response:', res.data)
    // Handle success
  },
  fail: (err) => {
    console.error('Request failed:', err)
    // Handle error
  }
})

// GET with query parameters
wx.request({
  url: 'https://api.example.com/search',
  method: 'GET',
  data: {
    q: 'mini program',
    page: 1,
    limit: 20
  },
  success: (res) => {
    console.log('Search results:', res.data)
  }
})

// Promise-based approach
function getUsers() {
  return new Promise((resolve, reject) => {
    wx.request({
      url: 'https://api.example.com/users',
      method: 'GET',
      success: resolve,
      fail: reject
    })
  })
}

// Usage with async/await
Page({
  async loadUsers() {
    try {
      const response = await getUsers()
      console.log('Users:', response.data)
      this.setData({
        users: response.data
      })
    } catch (error) {
      console.error('Failed to load users:', error)
    }
  }
})

POST Requests

javascript
// POST with JSON data
wx.request({
  url: 'https://api.example.com/users',
  method: 'POST',
  header: {
    'Content-Type': 'application/json'
  },
  data: {
    name: 'John Doe',
    email: 'john@example.com',
    age: 30
  },
  success: (res) => {
    console.log('User created:', res.data)
  },
  fail: (err) => {
    console.error('Failed to create user:', err)
  }
})

// POST with form data
wx.request({
  url: 'https://api.example.com/login',
  method: 'POST',
  header: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  data: 'username=john&password=secret123',
  success: (res) => {
    if (res.data.success) {
      // Store token
      wx.setStorageSync('token', res.data.token)
      console.log('Login successful')
    }
  }
})

PUT and DELETE Requests

javascript
// PUT request to update user
wx.request({
  url: 'https://api.example.com/users/123',
  method: 'PUT',
  header: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + wx.getStorageSync('token')
  },
  data: {
    name: 'John Smith',
    email: 'johnsmith@example.com'
  },
  success: (res) => {
    console.log('User updated:', res.data)
  }
})

// DELETE request
wx.request({
  url: 'https://api.example.com/users/123',
  method: 'DELETE',
  header: {
    'Authorization': 'Bearer ' + wx.getStorageSync('token')
  },
  success: (res) => {
    console.log('User deleted')
  }
})

Request Wrapper and Error Handling

Creating a Request Wrapper

javascript
// api.js - Centralized API wrapper
class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL
    this.defaultHeaders = {
      'Content-Type': 'application/json'
    }
  }
  
  // Get auth token
  getAuthToken() {
    return wx.getStorageSync('token') || ''
  }
  
  // Build headers
  buildHeaders(customHeaders = {}) {
    const headers = { ...this.defaultHeaders, ...customHeaders }
    
    const token = this.getAuthToken()
    if (token) {
      headers.Authorization = `Bearer ${token}`
    }
    
    return headers
  }
  
  // Generic request method
  request(options) {
    return new Promise((resolve, reject) => {
      const requestOptions = {
        url: this.baseURL + options.url,
        method: options.method || 'GET',
        data: options.data,
        header: this.buildHeaders(options.headers),
        timeout: options.timeout || 10000,
        success: (res) => {
          if (res.statusCode >= 200 && res.statusCode < 300) {
            resolve(res.data)
          } else {
            reject(this.handleError(res))
          }
        },
        fail: (err) => {
          reject(this.handleError(err))
        }
      }
      
      wx.request(requestOptions)
    })
  }
  
  // Handle errors
  handleError(error) {
    console.error('API Error:', error)
    
    if (error.statusCode === 401) {
      // Unauthorized - clear token and redirect to login
      wx.removeStorageSync('token')
      wx.showToast({
        title: 'Please login again',
        icon: 'error'
      })
      // Redirect to login page
      wx.redirectTo({ url: '/pages/login/login' })
    } else if (error.statusCode === 403) {
      wx.showToast({
        title: 'Access denied',
        icon: 'error'
      })
    } else if (error.statusCode >= 500) {
      wx.showToast({
        title: 'Server error',
        icon: 'error'
      })
    } else {
      wx.showToast({
        title: error.errMsg || 'Network error',
        icon: 'error'
      })
    }
    
    return error
  }
  
  // Convenience methods
  get(url, params = {}) {
    const queryString = Object.keys(params)
      .map(key => `${key}=${encodeURIComponent(params[key])}`)
      .join('&')
    
    const fullUrl = queryString ? `${url}?${queryString}` : url
    
    return this.request({
      url: fullUrl,
      method: 'GET'
    })
  }
  
  post(url, data = {}) {
    return this.request({
      url,
      method: 'POST',
      data
    })
  }
  
  put(url, data = {}) {
    return this.request({
      url,
      method: 'PUT',
      data
    })
  }
  
  delete(url) {
    return this.request({
      url,
      method: 'DELETE'
    })
  }
}

// Create API instance
const api = new ApiClient('https://api.example.com')

// Export for use in pages
module.exports = api

Using the API Wrapper

javascript
// In a page file
const api = require('../../utils/api')

Page({
  data: {
    users: [],
    loading: false
  },
  
  async onLoad() {
    await this.loadUsers()
  },
  
  async loadUsers() {
    this.setData({ loading: true })
    
    try {
      const users = await api.get('/users', {
        page: 1,
        limit: 20
      })
      
      this.setData({
        users,
        loading: false
      })
    } catch (error) {
      console.error('Failed to load users:', error)
      this.setData({ loading: false })
    }
  },
  
  async createUser(userData) {
    try {
      const newUser = await api.post('/users', userData)
      
      // Add to local list
      const users = [...this.data.users, newUser]
      this.setData({ users })
      
      wx.showToast({
        title: 'User created successfully',
        icon: 'success'
      })
    } catch (error) {
      console.error('Failed to create user:', error)
    }
  },
  
  async updateUser(userId, userData) {
    try {
      const updatedUser = await api.put(`/users/${userId}`, userData)
      
      // Update local list
      const users = this.data.users.map(user => 
        user.id === userId ? updatedUser : user
      )
      this.setData({ users })
      
      wx.showToast({
        title: 'User updated successfully',
        icon: 'success'
      })
    } catch (error) {
      console.error('Failed to update user:', error)
    }
  },
  
  async deleteUser(userId) {
    try {
      await api.delete(`/users/${userId}`)
      
      // Remove from local list
      const users = this.data.users.filter(user => user.id !== userId)
      this.setData({ users })
      
      wx.showToast({
        title: 'User deleted successfully',
        icon: 'success'
      })
    } catch (error) {
      console.error('Failed to delete user:', error)
    }
  }
})

File Upload and Download

File Upload

javascript
// Upload single file
function uploadFile(filePath, fileName) {
  return new Promise((resolve, reject) => {
    wx.uploadFile({
      url: 'https://api.example.com/upload',
      filePath,
      name: 'file',
      formData: {
        fileName,
        userId: wx.getStorageSync('userId')
      },
      header: {
        'Authorization': 'Bearer ' + wx.getStorageSync('token')
      },
      success: (res) => {
        const data = JSON.parse(res.data)
        resolve(data)
      },
      fail: reject
    })
  })
}

// Upload with progress
function uploadFileWithProgress(filePath, fileName, onProgress) {
  return new Promise((resolve, reject) => {
    const uploadTask = wx.uploadFile({
      url: 'https://api.example.com/upload',
      filePath,
      name: 'file',
      formData: { fileName },
      success: (res) => {
        const data = JSON.parse(res.data)
        resolve(data)
      },
      fail: reject
    })
    
    // Listen to upload progress
    uploadTask.onProgressUpdate((res) => {
      const progress = res.progress
      console.log('Upload progress:', progress + '%')
      
      if (onProgress) {
        onProgress(progress)
      }
    })
    
    return uploadTask
  })
}

// Usage in page
Page({
  data: {
    uploadProgress: 0
  },
  
  async chooseAndUploadImage() {
    try {
      // Choose image
      const res = await new Promise((resolve, reject) => {
        wx.chooseImage({
          count: 1,
          sizeType: ['compressed'],
          sourceType: ['album', 'camera'],
          success: resolve,
          fail: reject
        })
      })
      
      const filePath = res.tempFilePaths[0]
      
      // Upload with progress
      const result = await uploadFileWithProgress(
        filePath,
        'user-avatar.jpg',
        (progress) => {
          this.setData({ uploadProgress: progress })
        }
      )
      
      console.log('Upload successful:', result)
      
      wx.showToast({
        title: 'Upload successful',
        icon: 'success'
      })
      
      // Reset progress
      this.setData({ uploadProgress: 0 })
      
    } catch (error) {
      console.error('Upload failed:', error)
      wx.showToast({
        title: 'Upload failed',
        icon: 'error'
      })
    }
  }
})

File Download

javascript
// Download file
function downloadFile(url, fileName) {
  return new Promise((resolve, reject) => {
    wx.downloadFile({
      url,
      header: {
        'Authorization': 'Bearer ' + wx.getStorageSync('token')
      },
      success: (res) => {
        if (res.statusCode === 200) {
          // Save to local storage
          const fs = wx.getFileSystemManager()
          const localPath = `${wx.env.USER_DATA_PATH}/${fileName}`
          
          fs.saveFile({
            tempFilePath: res.tempFilePath,
            filePath: localPath,
            success: () => {
              resolve(localPath)
            },
            fail: reject
          })
        } else {
          reject(new Error('Download failed'))
        }
      },
      fail: reject
    })
  })
}

// Download with progress
function downloadFileWithProgress(url, fileName, onProgress) {
  return new Promise((resolve, reject) => {
    const downloadTask = wx.downloadFile({
      url,
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.tempFilePath)
        } else {
          reject(new Error('Download failed'))
        }
      },
      fail: reject
    })
    
    // Listen to download progress
    downloadTask.onProgressUpdate((res) => {
      const progress = res.progress
      console.log('Download progress:', progress + '%')
      
      if (onProgress) {
        onProgress(progress)
      }
    })
    
    return downloadTask
  })
}

// Usage
Page({
  data: {
    downloadProgress: 0
  },
  
  async downloadDocument() {
    try {
      const filePath = await downloadFileWithProgress(
        'https://api.example.com/files/document.pdf',
        'document.pdf',
        (progress) => {
          this.setData({ downloadProgress: progress })
        }
      )
      
      console.log('Download successful:', filePath)
      
      // Open file
      wx.openDocument({
        filePath,
        success: () => {
          console.log('Document opened')
        }
      })
      
      this.setData({ downloadProgress: 0 })
      
    } catch (error) {
      console.error('Download failed:', error)
      wx.showToast({
        title: 'Download failed',
        icon: 'error'
      })
    }
  }
})

WebSocket Connections

Basic WebSocket Usage

javascript
// WebSocket manager
class WebSocketManager {
  constructor(url) {
    this.url = url
    this.socket = null
    this.reconnectAttempts = 0
    this.maxReconnectAttempts = 5
    this.reconnectInterval = 3000
    this.messageQueue = []
    this.listeners = {}
  }
  
  connect() {
    return new Promise((resolve, reject) => {
      this.socket = wx.connectSocket({
        url: this.url,
        header: {
          'Authorization': 'Bearer ' + wx.getStorageSync('token')
        }
      })
      
      this.socket.onOpen(() => {
        console.log('WebSocket connected')
        this.reconnectAttempts = 0
        
        // Send queued messages
        this.messageQueue.forEach(message => {
          this.send(message)
        })
        this.messageQueue = []
        
        resolve()
      })
      
      this.socket.onMessage((res) => {
        try {
          const data = JSON.parse(res.data)
          this.handleMessage(data)
        } catch (error) {
          console.error('Failed to parse message:', error)
        }
      })
      
      this.socket.onClose(() => {
        console.log('WebSocket disconnected')
        this.handleDisconnect()
      })
      
      this.socket.onError((error) => {
        console.error('WebSocket error:', error)
        reject(error)
      })
    })
  }
  
  send(data) {
    if (this.socket && this.socket.readyState === 1) {
      this.socket.send({
        data: JSON.stringify(data)
      })
    } else {
      // Queue message if not connected
      this.messageQueue.push(data)
    }
  }
  
  handleMessage(data) {
    const { type, payload } = data
    
    if (this.listeners[type]) {
      this.listeners[type].forEach(callback => {
        callback(payload)
      })
    }
  }
  
  on(type, callback) {
    if (!this.listeners[type]) {
      this.listeners[type] = []
    }
    this.listeners[type].push(callback)
  }
  
  off(type, callback) {
    if (this.listeners[type]) {
      this.listeners[type] = this.listeners[type].filter(cb => cb !== callback)
    }
  }
  
  handleDisconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++
      console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`)
      
      setTimeout(() => {
        this.connect().catch(error => {
          console.error('Reconnection failed:', error)
        })
      }, this.reconnectInterval)
    } else {
      console.error('Max reconnection attempts reached')
    }
  }
  
  close() {
    if (this.socket) {
      this.socket.close()
      this.socket = null
    }
  }
}

// Usage in app or page
const wsManager = new WebSocketManager('wss://api.example.com/ws')

Page({
  onLoad() {
    this.initWebSocket()
  },
  
  onUnload() {
    wsManager.close()
  },
  
  async initWebSocket() {
    try {
      await wsManager.connect()
      
      // Listen for different message types
      wsManager.on('chat_message', (message) => {
        console.log('New chat message:', message)
        this.addChatMessage(message)
      })
      
      wsManager.on('user_online', (user) => {
        console.log('User came online:', user)
        this.updateUserStatus(user.id, 'online')
      })
      
      wsManager.on('notification', (notification) => {
        console.log('New notification:', notification)
        this.showNotification(notification)
      })
      
    } catch (error) {
      console.error('Failed to connect WebSocket:', error)
    }
  },
  
  sendChatMessage(message) {
    wsManager.send({
      type: 'chat_message',
      payload: {
        text: message,
        timestamp: Date.now()
      }
    })
  },
  
  addChatMessage(message) {
    const messages = [...this.data.messages, message]
    this.setData({ messages })
  },
  
  updateUserStatus(userId, status) {
    // Update user status in UI
  },
  
  showNotification(notification) {
    wx.showToast({
      title: notification.message,
      icon: 'none'
    })
  }
})

Request Caching and Optimization

Simple Cache Implementation

javascript
// cache.js - Simple request cache
class RequestCache {
  constructor(maxAge = 5 * 60 * 1000) { // 5 minutes default
    this.cache = new Map()
    this.maxAge = maxAge
  }
  
  generateKey(url, params = {}) {
    const paramString = Object.keys(params)
      .sort()
      .map(key => `${key}=${params[key]}`)
      .join('&')
    
    return `${url}?${paramString}`
  }
  
  set(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    })
  }
  
  get(key) {
    const cached = this.cache.get(key)
    
    if (!cached) {
      return null
    }
    
    // Check if expired
    if (Date.now() - cached.timestamp > this.maxAge) {
      this.cache.delete(key)
      return null
    }
    
    return cached.data
  }
  
  clear() {
    this.cache.clear()
  }
  
  delete(key) {
    this.cache.delete(key)
  }
}

// Enhanced API client with caching
class CachedApiClient extends ApiClient {
  constructor(baseURL) {
    super(baseURL)
    this.cache = new RequestCache()
  }
  
  async get(url, params = {}, useCache = true) {
    const cacheKey = this.cache.generateKey(url, params)
    
    // Try cache first
    if (useCache) {
      const cached = this.cache.get(cacheKey)
      if (cached) {
        console.log('Cache hit for:', cacheKey)
        return cached
      }
    }
    
    // Make request
    try {
      const data = await super.get(url, params)
      
      // Cache successful response
      if (useCache) {
        this.cache.set(cacheKey, data)
      }
      
      return data
    } catch (error) {
      throw error
    }
  }
  
  // Invalidate cache for specific patterns
  invalidateCache(pattern) {
    for (const key of this.cache.cache.keys()) {
      if (key.includes(pattern)) {
        this.cache.delete(key)
      }
    }
  }
}

const api = new CachedApiClient('https://api.example.com')
module.exports = api

Request Batching

javascript
// Batch multiple requests
class BatchRequestManager {
  constructor(batchSize = 5, delay = 100) {
    this.batchSize = batchSize
    this.delay = delay
    this.queue = []
    this.timer = null
  }
  
  add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        ...request,
        resolve,
        reject
      })
      
      this.scheduleExecution()
    })
  }
  
  scheduleExecution() {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    
    this.timer = setTimeout(() => {
      this.executeBatch()
    }, this.delay)
    
    // Execute immediately if batch is full
    if (this.queue.length >= this.batchSize) {
      clearTimeout(this.timer)
      this.executeBatch()
    }
  }
  
  async executeBatch() {
    if (this.queue.length === 0) return
    
    const batch = this.queue.splice(0, this.batchSize)
    
    try {
      // Execute all requests in parallel
      const promises = batch.map(request => {
        return wx.request({
          url: request.url,
          method: request.method,
          data: request.data,
          header: request.header
        })
      })
      
      const results = await Promise.allSettled(promises)
      
      // Resolve/reject individual promises
      results.forEach((result, index) => {
        const request = batch[index]
        
        if (result.status === 'fulfilled') {
          request.resolve(result.value)
        } else {
          request.reject(result.reason)
        }
      })
      
    } catch (error) {
      // Reject all requests in batch
      batch.forEach(request => {
        request.reject(error)
      })
    }
    
    // Continue with next batch if queue is not empty
    if (this.queue.length > 0) {
      this.scheduleExecution()
    }
  }
}

const batchManager = new BatchRequestManager()

// Usage
Page({
  async loadMultipleResources() {
    try {
      const [users, posts, comments] = await Promise.all([
        batchManager.add({
          url: 'https://api.example.com/users',
          method: 'GET'
        }),
        batchManager.add({
          url: 'https://api.example.com/posts',
          method: 'GET'
        }),
        batchManager.add({
          url: 'https://api.example.com/comments',
          method: 'GET'
        })
      ])
      
      console.log('All resources loaded:', { users, posts, comments })
      
    } catch (error) {
      console.error('Failed to load resources:', error)
    }
  }
})

Best Practices

Network Status Monitoring

javascript
// Network monitor
class NetworkMonitor {
  constructor() {
    this.isOnline = true
    this.networkType = 'unknown'
    this.listeners = []
    
    this.init()
  }
  
  init() {
    // Get initial network status
    wx.getNetworkType({
      success: (res) => {
        this.networkType = res.networkType
        this.isOnline = res.networkType !== 'none'
      }
    })
    
    // Listen for network changes
    wx.onNetworkStatusChange((res) => {
      this.isOnline = res.isConnected
      this.networkType = res.networkType
      
      this.notifyListeners({
        isOnline: this.isOnline,
        networkType: this.networkType
      })
    })
  }
  
  onStatusChange(callback) {
    this.listeners.push(callback)
  }
  
  notifyListeners(status) {
    this.listeners.forEach(callback => {
      callback(status)
    })
  }
  
  isSlowNetwork() {
    return ['2g', '3g'].includes(this.networkType)
  }
}

const networkMonitor = new NetworkMonitor()

// Usage in app.js
App({
  onLaunch() {
    networkMonitor.onStatusChange((status) => {
      console.log('Network status changed:', status)
      
      if (!status.isOnline) {
        wx.showToast({
          title: 'Network disconnected',
          icon: 'error'
        })
      } else {
        wx.showToast({
          title: 'Network connected',
          icon: 'success'
        })
      }
    })
  }
})

Request Retry Logic

javascript
// Retry wrapper
function withRetry(requestFn, maxRetries = 3, delay = 1000) {
  return async function(...args) {
    let lastError
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await requestFn(...args)
      } catch (error) {
        lastError = error
        
        console.log(`Request failed (attempt ${attempt}/${maxRetries}):`, error)
        
        // Don't retry on client errors (4xx)
        if (error.statusCode >= 400 && error.statusCode < 500) {
          throw error
        }
        
        // Wait before retry (exponential backoff)
        if (attempt < maxRetries) {
          const waitTime = delay * Math.pow(2, attempt - 1)
          await new Promise(resolve => setTimeout(resolve, waitTime))
        }
      }
    }
    
    throw lastError
  }
}

// Usage
const apiWithRetry = {
  getUsers: withRetry(api.get.bind(api)),
  createUser: withRetry(api.post.bind(api)),
  updateUser: withRetry(api.put.bind(api))
}

Page({
  async loadUsers() {
    try {
      const users = await apiWithRetry.getUsers('/users')
      this.setData({ users })
    } catch (error) {
      console.error('Failed to load users after retries:', error)
    }
  }
})

Network requests are fundamental to modern mini programs. Always handle errors gracefully, implement proper caching strategies, and consider network conditions to provide the best user experience.

Connecting Multiple Platforms, Empowering Innovation