如何优化编写「快应用」数据存储(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)
}
弊端可谓显而易见,那么该如何优化下呢?
更为推荐的写法
大致的思路是,要充分利用 Promise
对 storage
API 进行封装;再借助 async
、await
,对异步代码进行链式调用,从而使得代码去除冗杂的回调,使其更加清晰、简洁。具体步骤如下:
封装 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 有完整呈现,感兴趣的朋友,可了解一下。