如何优化编写「快应用」数据存储(storage)?

查阅快应用开发文档,可以知道官方提供了诸多接口 ;对于如何使用,文档中也给出了简单的说明和代码示例;但照着示例写法,在实际项目中,不够优雅,且更不高效;所以,在开发中需要加以变通去写,使得大幅提升开发效率,同时也令整个代码优雅,以便于维护。早期有写文章: 如何优雅使用「快应用」API ,探讨了优雅调用 API。就其数据存储(storage),发现也有必要单开一篇,以探讨如何优雅编写相关代码。

不太优雅的写法

在官方文档: 数据存储(storage) ,给出了如下编写示例:

import storage from '@system.storage'
storage.get({
  key: 'YOUR-KEY',
  success: function(data) {
    console.log('handling success')
  },
  fail: function(data, code) {
    console.log(`handling fail, code = ${code}`)
  },
  complete: function(data) {
    console.log(`handling complete`, data)
  }
})

这种写法本身呢,倒也没什么问题,只是显得很有历史感(属于当代不推荐的写法);但,遇到些奇特的诉求,这种写法的劣势就非常明显,给初级开发者,做了极其不负责任的示范。譬如说,可能处于某种诉求考虑,就有可能写出类似如下代码(示例 X Code):

import storage from '@system.storage'

onInit() {
    setTimeout(() => {
        storage.get({
          key: 'YOUR-KEY',
          success: function(data) {
            console.log('handling success')
            setTimeout(() => {
                // do something again.
            }, 100)
          },
          fail: function(data, code) {
            console.log(`handling fail, code = ${code}`)
          },
          complete: function(data) {
            console.log(`handling complete`, data)
          }
        })
    }, 100)
}

弊端可谓显而易见,那么该如何优化下呢?

更为推荐的写法

大致的思路是,要充分利用 Promisestorage API 进行封装;再借助 asyncawait ,对异步代码进行链式调用,从而使得代码去除冗杂的回调,使其更加清晰、简洁。具体步骤如下:

封装 storage API

// helper/storage.js
import storage from '@system.storage'
import prompt from '@system.prompt'

export default {
  set: (key, value) => {
    return new Promise((resolve, reject) => {
      return storage.set({
        key,
        value,
        success: function(data) {
	      // 统一对存储的内容,做处理;
          resolve(data)
        },
        fail: function(data, code) {
          console.log(`Somthing Error[@storage set]: data = ${data}`)
          reject(data)
        }
      })
    })
  },

  get: key => {
    return new Promise((resolve, reject) => {
      return storage.get({
        key,
        success: function(data = '[]') {
          try {
            resolve(JSON.parse(data || '[]') || {})
          } catch (error) {
            prompt.showToast({
              message: `获取 storage 数据出现问题,${error.message}`
            })
            console.log(`Somthing Error[@storage get]: error = ${error}`)
            resolve([])
          }
        },
        fail: function(data, code) {
          console.log(`Somthing Error[@storage get]: data = ${data}`)
          reject(data)
        }
      })
    })
  }
}

改为链式调用

再说明链式调用之前,有必要对 setTimeout 方法做下说明;改函数会在计时器到期后执行;setTimeout() 是一个异步函数,这意味着定时器函数不会暂停函数堆栈中其他函数的执行。换句话说,您不能 setTimeout() 在函数堆栈中的下一个函数触发之前创建“暂停”。为程序简洁化考虑,也可以对其进行封装,以在合适场景,按需使用。

// 方法 1
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 方法 2
const sleep = m => new Promise(r => setTimeout(r, m))

// 或直接使用
await new Promise(resolve => setTimeout(resolve, 1000));

经过如上优化之后,先前的示例 X Code 中代码,可优化为如下代码:

import storage from "/helper/storage"

async onInit() {
    await sleep(100)
    const data = await storage.get({ key })
    await sleep(100)
    // do something again.
}

最后,回顾下这样写所带来的优势:

  • 移除不必要回调,使整个逻辑,更加清晰、简洁、优雅;
  • 对 Storage API 做了封装, 可统一对存储的内容做处理;

当然,这种思路并不仅限于快应用开发, 在 Web 、小程序开发中,同样适用。以上优化,在开源项目 graceful-sentences-quickapp 有完整呈现,感兴趣的朋友,可了解一下。

您可能会感兴趣的文章