在小程序开发中,网络异步请求是最常用的功能之一,但也是新手最容易踩坑的地方。回调嵌套、请求时序混乱、数据渲染失败…… 这些问题本质上都是没处理好异步逻辑。

本文将带你从基础到进阶,彻底解决小程序异步请求问题,写出优雅、健壮、易维护的代码。

一、先搞懂:小程序异步请求的核心特性

小程序的网络请求 wx.request异步非阻塞的:

  1. 发起请求后,代码不会等待请求结果,会继续向下执行
  2. 接口响应成功 / 失败后,才会触发对应的回调函数
  3. 异步操作无法直接用 return 返回结果,只能通过回调 / Promise/async-await 处理

这就是为什么你直接赋值、打印会出现数据 undefined 的根本原因。


二、新手必踩:回调地狱(Callback Hell)

最原始的写法是嵌套回调,简单需求能用,但复杂场景直接崩盘:

javascript

运行

// 错误示范:嵌套回调 = 回调地狱
wx.request({
  url: 'https://api.com/getUser',
  success: res => {
    // 第一层回调
    wx.request({
      url: 'https://api.com/getOrder',
      data: { userId: res.data.id },
      success: res2 => {
        // 第二层回调
        wx.request({
          url: 'https://api.com/getGoods',
          data: { orderId: res2.data.id },
          success: res3 => {
            // 第三层回调... 嵌套越来越深
          },
          fail: err => {}
        })
      },
      fail: err => {}
    })
  },
  fail: err => {}
})

问题:

  1. 代码嵌套层级极深,可读性极差
  2. 异常捕获困难,一处报错全线崩溃
  3. 维护成本极高,改代码像拆炸弹

三、进阶方案 1:Promise 封装(告别嵌套)

我们可以把 wx.request 封装成Promise,用链式调用替代嵌套:

1. 封装通用请求工具类

utils/request.js 创建封装函数:

javascript

运行

// 封装Promise版请求
const request = (options) => {
  // 加载提示(可选)
  wx.showLoading({ title: '加载中...' })
  
  return new Promise((resolve, reject) => {
    wx.request({
      // 基础域名(统一管理)
      url: 'https://api.xxx.com' + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'content-type': 'application/json',
        // 统一携带token
        'token': wx.getStorageSync('token') || ''
      },
      success: res => {
        // 统一处理响应
        if (res.statusCode === 200) {
          resolve(res.data)
        } else {
          wx.showToast({ title: '请求失败', icon: 'none' })
          reject(res)
        }
      },
      fail: err => {
        wx.showToast({ title: '网络异常', icon: 'none' })
        reject(err)
      },
      complete: () => {
        wx.hideLoading()
      }
    })
  })
}

module.exports = { request }

2. 页面中使用链式调用

javascript

运行

const { request } = require('../../utils/request.js')

// 页面JS
Page({
  getData() {
    // 链式调用,层级扁平化
    request({ url: '/getUser' })
      .then(user => {
        console.log('用户信息', user)
        return request({ url: '/getOrder', data: { userId: user.id } })
      })
      .then(order => {
        console.log('订单信息', order)
        return request({ url: '/getGoods', data: { orderId: order.id } })
      })
      .then(goods => {
        console.log('商品信息', goods)
        this.setData({ goods })
      })
      .catch(err => {
        // 统一捕获所有异常
        console.log('请求异常', err)
      })
  }
})

优点

  • 代码扁平化,无嵌套
  • 统一异常捕获
  • 可维护性大幅提升

四、终极方案 2:async/await 同步写法(推荐)

ES7 的 async/await异步编程的终极解决方案,写法和同步代码一样,逻辑最清晰。

使用方式(基于上面的 Promise 封装)

javascript

运行

const { request } = require('../../utils/request.js')

Page({
  // 1. 方法前加 async
  async getDetail() {
    try {
      // 2. 请求前加 await
      const user = await request({ url: '/getUser' })
      const order = await request({ url: '/getOrder', data: { userId: user.id } })
      const goods = await request({ url: '/getGoods', data: { orderId: order.id } })
      
      // 3. 直接赋值渲染
      this.setData({ user, order, goods })
      
    } catch (err) {
      // 4. 统一捕获异常
      console.log('请求失败:', err)
      wx.showToast({ title: '数据加载失败', icon: 'none' })
    }
  }
})

核心规则:

  1. 函数必须声明 async
  2. 异步请求前加 await,代码会等待请求完成再向下执行
  3. try/catch 捕获所有请求异常
  4. 可以直接用变量接收请求结果,像同步代码一样使用

这是目前小程序异步请求的最佳实践,企业级开发首选!


五、高频场景:并行请求(同时发起多个接口)

如果多个接口没有依赖关系,不需要等待上一个完成,用 Promise.all 并行执行:

javascript

运行

async getAllData() {
  try {
    // 同时发起3个请求,全部完成后统一接收结果
    const [user, order, goods] = await Promise.all([
      request({ url: '/getUser' }),
      request({ url: '/getOrder' }),
      request({ url: '/getGoods' })
    ])
    
    this.setData({ user, order, goods })
    
  } catch (err) {
    console.log('请求异常', err)
  }
}

优势:大幅缩短请求时间,提升页面加载速度


六、实战避坑:小程序异步请求常见问题

1. 数据渲染不出来?

原因:异步请求未完成,就执行了 setData,数据还是 undefined解决方案:必须在请求成功后(then/await 后)再赋值

2. onLoad 中请求不执行?

原因onLoad 是同步执行,不会等待异步请求解决方案onLoad 中调用 async 方法即可

javascript

运行

onLoad() {
  this.getDetail() // 调用async方法
}

3. 连续请求导致数据错乱?

原因:上一个请求未完成,又发起新请求,响应时序不确定解决方案

  • 加防抖 / 节流
  • 页面卸载时中断请求

七、完整最佳实践代码

1. 工具类 utils/request.js

javascript

运行

const request = (options) => {
  wx.showLoading({ title: '加载中...' })
  return new Promise((resolve, reject) => {
    wx.request({
      url: 'https://api.xxx.com' + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'content-type': 'application/json',
        'token': wx.getStorageSync('token') || ''
      },
      success: res => {
        res.statusCode === 200 ? resolve(res.data) : reject(res)
      },
      fail: err => reject(err),
      complete: () => wx.hideLoading()
    })
  })
}
module.exports = { request }

2. 页面 JS

javascript

运行

const { request } = require('../../utils/request.js')
Page({
  data: {
    detail: {}
  },

  onLoad() {
    this.getPageData()
  },

  // 终极异步方案
  async getPageData() {
    try {
      // 串行请求
      const info = await request({ url: '/page/detail' })
      this.setData({ detail: info })
      
      // 并行请求
      const [banner, list] = await Promise.all([
        request({ url: '/banner' }),
        request({ url: '/list' })
      ])
      this.setData({ banner, list })
      
    } catch (err) {
      wx.showToast({ title: '加载失败', icon: 'none' })
      console.error(err)
    }
  }
})

八、总结

小程序异步请求的进化之路:

  1. 原始回调 → 嵌套地狱,不推荐
  2. Promise 链式 → 扁平化,中等推荐
  3. async/await → 同步写法,终极方案,强烈推荐

核心口诀:

  • 封装请求用 Promise
  • 书写逻辑用 async/await
  • 异常捕获用 try/catch
  • 无依赖请求用 Promise.all

掌握这套方案,你就能彻底告别小程序异步请求的所有坑,写出企业级优雅代码!


总结

  1. 小程序wx.request是异步操作,不能直接 return 结果,这是数据 undefined 的根源
  2. 回调嵌套会产生回调地狱,绝对不推荐用于复杂业务
  3. 最优解:Promise 封装 + async/await,同步写法处理异步逻辑,清晰易维护
  4. 并行无依赖请求用Promise.all,提升加载效率
  5. 统一异常捕获 + 请求处理,让代码更健壮
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐