先來看一段代碼,就是一小段而已:
export function loginWithWx() {
wx.showLoading({ title: "登錄中..." });
wx.login({
success: res => {
wx.request({
url: `${apiRoot}wx/${res.code}`,
method: "get",
success: res => {
const { data } = res;
const jwt = app.globalData.jwt = data?.jwt;
if (jwt) {
wx.reLaunch({ url: "../index/index" });
wx.hideLoading();
}
else {
showMessage(data.message || "登錄時發生錯誤");
wx.hideLoading();
}
},
fail: res => {
showMessage("請求超時,請稍後重試");
}
});
wx.hideLoading();
},
fail: res => {
console.log(res);
}
});
wx.hideLoading();
}
這段代碼乍一看,似乎沒毛病。但是稍微思考一下,就能發現問題了。
首先,最直觀的問題:縮進太深。縮進最深的地方是 24 個空格,也就是 6 層。一般我們認爲 3 層以內的縮進比較容易閱讀,超過 3 層應該考慮使用“Extract Method”方法進行重構。
接下來,看外層邏輯:
wx.showLoading()
wx.login()
wx.hideLoading()
這是期望的執行順序。
注意到 wx.login
是一個異步過程,所以實際上 hideLoading()
並不會等登錄過程結束就關閉了加載提示。所以第 2 個問題是忽略了異步執行的順序。
馬上可以想到使用 wx.login()
的 complete
參數來解決:
wx.showLoading();
wx.login({
complete: () => wx.hideLoading()
});
不過馬上就引出了下一個問題:complete 還是太快!
爲什麼?我們再把內部的邏輯結構清理出來:
wx.login({
success: () => {
wx.request({
success: () => { },
fail: () => { }
})
},
fail: () => { }
})
注意到 wx.request
仍然是一個異步過程,所以 wx.login
的 success
會立即結束,觸發 complete
。而這時候 wx.request
可能還在等待服務器響應。
那麼是不是應該把 wx.hideLoading()
放到內部邏輯中去?理論上來說,是的!
但實際情況是,內部邏輯分支較多,深次較深,既有同步邏輯,也有異步邏輯……考慮應該放在哪些地方,需要非常的謹慎。實際上,案例中的代碼就已經在內部邏輯中放了一些 wx.hideLoading()
,只不過
覆蓋不全;
因爲最外層的
hideLoading()
提前執行,失效了。違反了規範性約束:成對邏輯應該儘量避免一對多的情況。
解釋一下第 3 點,就是說:一個 showLoading()
最好只對應一個 hideLoading()
。考慮到邏輯的複雜性,這不是強制約束規則,但應該儘量去避免。
處理的辦法是,重構,將內部邏輯拆分出來;然後,將完成事件處理邏輯作爲一個參數,一層層的往裏傳:
function appLogin(params, complete) {
// ^^^^^^^^
wx.request({
...params,
complete: complete
// ^^^^^^^^
});
}
function wxLogin(params, complete) {
// ^^^^^^^^
wx.login({
...params,
success: () => appLogin({}, complete),
// ^^^^^^^^
fail: () => complete()
// ^^^^^^^^^^
// complete: complete // ✗
// 注意:由於 success 和 fail 裏存在異步處理,不能直接使用 complete 事件。
// 原因在前面已經說了。
});
}
wx.showLoading();
wxLogin({}, () => wx.hideLoading());
// ^^^^^^^^^^^^^^^^^^^^^^ 傳入的 complete
顯然在當前的技術環境中,這並不是最優方案,還可以繼續優化——反正都要封裝,乾脆封裝成 Promise。然後通過 await
調用轉換成同步語法,處理起來會輕鬆得多。封裝的具體過程在前兩篇文章中有詳細的講解,這裏就不贅述了。總之,我們封裝了 wx
的異步版本 awx
,在這裏用就好:
export async function asyncLoginWithWx() {
wx.showLoading({ title: "登錄中..." });
try {
return await internalProcess();
} catch (err) {
showMessage("請求超時,請稍後重試");
} finally {
wx.showLoading();
}
// 把內部邏輯用個局部函數封裝起來,
// 主要是爲了讓 try ... catch ... 看起來清晰一些
async function internalProcess() {
const { code } = await awx.login();
const { data } = awx.request({
url: `${apiRoot}wx/${code}`,
method: "get",
});
const jwt = app.globalData.jwt = data?.jwt;
if (jwt) {
wx.reLaunch({ url: "../index/index" });
} else {
showMessage(data.message || "登錄時發生錯誤");
}
}
}
喜歡此文,點個在看 ⇘
支持作者,賞個咖啡豆 ⇓
本文分享自微信公衆號 - 邊城客棧(fancyidea-full)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。