收藏
回答

可以支持异步 setData 吗?

目前在 async/await 中参入回调式 setData 函数显得非常别扭,举个列子,我希望在请求完成后不论成功与否都处理一些单独的业务逻辑:

try {
  const { data: res } = await fetch.getRes('/get', { page: 1, limit: 10 })
  if (res.code === 0) {
    this.setData({ count: res.data.count, list: res.data.list }, () => {
      doSomeThings1()
      doSomeThings2()
    })
  } else {
    wx.showToast({ title: res.data.msg, icon: 'none' })
    doSomeThings1()
    doSomeThings2()
  }
} catch (err) {
  wx.showToast({ title: '服务器开小差了', icon: 'none' })
  doSomeThings1()
  doSomeThings2()
}


会看到上面出现了大量重复代码,增加代码阅读成本,降低开发效率。当然,可以单独封装一下逻辑,例如下面这样:

const doThings = () => {
  doSomeThings1()
  doSomeThings2()
}

try {
  const { data: res } = await fetch.getRes('/get', { page: 1, limit: 10 })
  if (res.code === 0) {
    this.setData({ count: res.data.count, list: res.data.list }, doThings)
  } else {
    wx.showToast({ title: res.data.msg, icon: 'none' })
    doThings()
  }
} catch (err) {
  wx.showToast({ title: '服务器开小差了', icon: 'none' })
  doThings()
}


这时候仍然可以看到出现了三次重复的逻辑。

如果有 setDataAsync 方法,代码看上去会像下面这样更加简洁:

try {
  const { data: res } = await fetch.getRes('/path', { page: 1, limit: 10 })
  if (res.code === 0) {
    await this.setDataSync({ count: res.data.count, list: res.data.list })
  } else {
    wx.showToast({ title: res.data.msg, icon: 'none' })
  }
} catch (err) {
  wx.showToast({ title: '服务器开小差了', icon: 'none' })
}

doSomeThings1()
doSomeThings2()


当然,也许可以自己定义一个 setDataAsync 方法,像这样:

Page({
  /**
   * 异步设置数据
   * @param {Record<string, any>} data
   * @returns {Promise<void>}
   */
  setDataAsync(data) {
    return new Promise(resolve => void this.setData(data, resolve))
  }
})


可是上面这么做存在一些问题:

  1. 每个页面/组件都需要手动写上该方法,仍然会增加大量的重复代码。
  2. 结合 TypeScript 时,无法和 setData 一样获得良好的代码补全(setData 方法可以获得在 data 中定义的属性并自动补全)。


以上是目前我开发时遇到的问题,目前能想到的方案是支持异步 setData,可以考虑两种方式:

  1. 让原有的 setData 返回 Promise,原有 callback 保持不变。
  2. 新增 setDataAsync 异步方法。

个人认为第一种方式更好些,希望开发团队考虑下。


回答关注问题邀请回答
收藏

2 个回答

  • LastLeaf
    LastLeaf
    2022-03-28

    有个基础的说明: setData 总体上说是同步的。绝大多数情况下,不需要等到 setData 回调再执行之后的逻辑。(那样反而会拖慢整体的流程,因为 setData 的回调是在渲染线程已经确认界面更新之后,有时会非常非常晚。)

    2022-03-28
    有用 1
    回复 4
    • momo
      momo
      发表于移动端
      2022-03-28
      了解了,就是说除非后续代码依赖视图层渲染后的结果,其他情况都可以当做同步方法去使用,我这么理解没错吧? 目前没有遇到依赖视图渲染后的逻辑,所以我的问题目前解决了,但对于其他开发者来说,确实会有这个需求的情况,就像 callback 是有必要的那样,async 里面需要 await 也是有需要的。
      2022-03-28
      回复
    • 卢霄霄
      卢霄霄
      2022-03-29
      其实需求是 setData 的渲染成功,能通过 promise 返回,而不是单纯通过回调返回,和流程无关啊。比如 wx.request ,wx.onSocketOpen 这些都已经支持了的呢。
      2022-03-29
      回复
    • LastLeaf
      LastLeaf
      2022-03-29回复momo
      甚至大部分依赖视图层渲染结果的调用都可以同步调用,比如 createSelectorQuery
      2022-03-29
      回复
    • LastLeaf
      LastLeaf
      2022-03-29回复卢霄霄
      本质上我们是不想鼓励异步依赖试图层结果的。
      2022-03-29
      回复
  • brave
    brave
    2022-03-28

    Ps: 未实际测试,可以参考下

    你可以挂载 setDataAsync 方法到 app 上

    App({
      setDataAsync(page, data) {
        return new Promise(resolve => page.setData(data, resolve))
      }
    })
    // page.js
    getApp().setDataAsync(this, {
      test: true
    })
    

    你也可以重写Page来实现每个页面都默认均带 setDataAsync 方法

    // wxPage.js
    const wxPage = () => {
      const originalPage = Page
    
      Page = (config) => {
        config.setDataAsync = function(data) {
            return new Promise(resolve => config.setData(data, resolve))
        }
        return originalPage(config)
      }
    }
    module.exports = wxPage()
    
    require("./wxPage.js")
    // page.js
    this.setDataAsync({
      test: true
    })
    
    2022-03-28
    有用 3
    回复 1
    • momo
      momo
      2022-03-28
      感谢回复!我试过类似挂载到 app 的方式,把 setDataAsync 单独写到了 js 中再引入,但是这么做会导致无法通过类型检查,并且也是去了代码提示,所以提了这个问题。后面一个方式学到了,虽然还没有测试,但看起来似乎也无法通过 TypeScript 类型检查。是个不错的方法,如果官方原生支持就可以避免这些问题是个更好的方案了。
      2022-03-28
      回复
登录 后发表内容