用vs做網(wǎng)站如何連接數(shù)據(jù)庫最新國內(nèi)你新聞
文章目錄
- 背景
- 整理思路
- V1版本
- V2版本
- V3版本
背景
最近在寫uniapp,發(fā)現(xiàn)執(zhí)行網(wǎng)絡(luò)請求的時候經(jīng)常要處理Loading效果。
比如,在發(fā)送網(wǎng)絡(luò)請求之前,觸發(fā)Loadng;無論請求成功還是失敗都要關(guān)閉Loading;請求失敗的時候我們還要給用戶一個友好的提示,比如“服務(wù)器打了個盹”。
每次都要手動處理,真的很麻煩;而且處理Loading的邏輯跟業(yè)務(wù)邏輯混在一起,也不便于維護。
因此,我打算把這個需求封裝成要一個方法,就叫loadingRun
。
這個方法并不是一蹴而就的,經(jīng)歷了幾個版本的迭代。我強烈建議你看完這篇文章,你一定能有所收獲。
雖然是以uniapp代碼演示的,但是思路是通用的。
整理思路
在封裝之前,我們先看看封裝之前代碼長啥樣:
uni.showLoading({mask: true})
uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true}).then(res => {Object.assign(order, res.result.data)uni.hideLoading()
}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))
}).finally(_ => uni.hideLoading())
有些小伙伴可能不熟悉uniapp,所以我們解釋一下部分代碼:
uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true})
這是uniapp的uniCloud的操作,執(zhí)行的是網(wǎng)絡(luò)請求,返回的是一個promise。uni.showLoading()
uniapp 提供的顯示Loading的api。uni.hideLoading()
uniapp 提供的關(guān)閉Loading的api。uni.showToast()
uniapp 提供的顯示Toast提示api。
這些你都可以想象成自己熟悉的UI框架的操作。
我們需要把這個方法抽成一個通用的loadingRun
方法,首先不管三七二十一,我們先把這個方法寫出來:
function loadingRun() {uni.showLoading({mask: true})uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true}).then(res => {Object.assign(order, res.result.data)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
然后我們就想,有哪些操作是可以提取出來,作為loadingRun
的參數(shù)。
最容易想到的就是Object.assign(order, res.result.data)
這行代碼,這行代碼是promise解決之后處理邏輯,我們可以把它提取成一個resolve參數(shù):
function loadingRun(resolve) {uni.showLoading({mask: true})uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true}).then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
還有什么可以提取的呢?uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true})
這個操作我們可以提取,因為的網(wǎng)絡(luò)操作是不固定的。它是一個promise,我們先不管三七二十一,先用一promise來接收:
function loadingRun(promise, resolve) {uni.showLoading({mask: true})promise.then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
還能再抽嗎?貌似toast提醒的文案可以抽出來,不過我們整站的提示都是統(tǒng)一的,所以就不抽了。
現(xiàn)在就可以了嗎?顯然不是,它還有問題,有大問題,我們接著聊。
V1版本
我們前面講了封裝loadingRun
的思路,就是“抽”、“抽”、“抽”,先不管合理不合理,先抽再說。這樣可以讓我們最快的得到一個基本的骨架:
function loadingRun(promise, resolve) {uni.showLoading({mask: true})promise.then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
看著不錯,實際有大問題,你知道是啥問題嗎?
問題就是loadingRun
直接接收了一個promise實例。
為啥直接接收就有問題呢?因為promise是創(chuàng)建時立即執(zhí)行的,所以loadingRun
執(zhí)行的時候,promise可能已經(jīng)執(zhí)行完了,網(wǎng)絡(luò)請求可能都返回了,此時在才顯示Loading,黃瓜菜都涼了。
所以loadingRun
不能從外部接收一個promise,我們需要在loadingRun
內(nèi)部創(chuàng)建這個promise。
我們可以讓外部傳入一個方法promiseGenerator
,這個方法執(zhí)行會產(chǎn)生一個promise:
function loadingRun(promiseGenerator, resolve) {uni.showLoading({mask: true})promiseGenerator().then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
至此我們獲得了V1版本的loadingRun
,我們前面代碼可以用loadingRun
來重構(gòu)一下:
loadingRun(_ => uniCloud.database().collection('orders').doc(props.orderId).get({getOne: true}), res => Object.assign(order, res.result.data))
看起來沒那么清晰對吧,我們再改進一下:
// 把獲取訂單的操作封裝到一個方法中
function getOrderAsync(id) {return uniCloud.database().collection('orders').doc(id).get({getOne: true})
}
// 封裝處理邏輯
function reactiveSetOrder(res) {Object.assign(order, res.result.data)
}
現(xiàn)在的loadingRun
就清晰了:
loadingRun(getOrderAsync(props.id), reactiveSetOrder)
當然后面這個封裝,不屬于我門今天討論的范疇。
V2版本
什么,還有V2版本,loadingRun
還可以再優(yōu)化嗎?
還真是!現(xiàn)在的loadingRun
接收一個promiseGenerator
,promiseGenerator
產(chǎn)生一個promise實例。
如果我有一個很耗時的方法,但是它不返回promise,它也想用loadingRun
怎么辦呢?
現(xiàn)在V1版本還不夠通用,我們需要讓它變得更加通用。
我們可以這么處理,我們把promiseGenerator
參數(shù),換成更通用的func
參數(shù)。如果它的結(jié)果是一個promise,那么就走原來的邏輯;否則,我們把func
的執(zhí)行結(jié)果用Promise.resolve
封裝成一個promise,走之前的邏輯。
這個叫參數(shù)歸一化!
我們先定一個方法判斷變量是不是一個promise:
function isPromiseLike(value) {return value != null && (typeof value === 'object' || typeof value === 'function')&& typeof value.then === 'function'
}
這里并不是直接用instanceof Promise
判斷,而是判斷變量是不是滿足Promise A+規(guī)范。
接下來我們改造loadingRun
:
function loadingRun(func, resolve) {uni.showLoading({mask: true})let ret = func()let promise = isPromiseLike(ret) ? ret : Promise.resolve(ret)promise.then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())
}
現(xiàn)在我們可以用loadingRun
處理耗時的非promise操作了,比如計算斐波那契數(shù)列:
loadingRun(_ => fibonacciRecursive(100000), val => console.log(`計算結(jié)果:${val}`))
V3版本
啊!還能再優(yōu)化嗎?
還真實?,F(xiàn)在的loadingRun
已經(jīng)夠用了,但是還有一個場景滿足不了。
比如,當網(wǎng)絡(luò)請求或者耗時操作報錯了,我希望除了提示Toast,還要執(zhí)行一個其他的操作,現(xiàn)在就沒辦法,因為promise實例沒有返回出來。
因此改造也很簡單,只需要把promise返回出來就行了。
function loadingRun(func, resolve) {uni.showLoading({ mask: true })let ret = func()let promise = isPromiseLike(ret) ? ret : Promise.resolve(ret)promise.then(res => {resolve(res)uni.hideLoading()}).catch(_ => {uni.hideLoading().then(_ => uni.showToast({icon: "fail",title: "服務(wù)器打了個盹"}))}).finally(_ => uni.hideLoading())return promise
}
然后我們就可以這樣處理:
loadingRun(getOrderAsync(props.id), reactiveSetOrder).catch(_ => doSomething())
Game Over !!!