相信很多開發者都曾經遇到過回檔地獄的問題。由於微信小程式的API基本上都是基於回呼函數的非同步操作,如果不使用其他框架或封裝API,特別是使用較多的wx.request(),基本上很快就會遇到回呼地獄的問題,維護起來十分痛苦。
舉個例子
假設此時在正在開發一個社交小程序,其中有一個功能的是,小程序用戶在登入後,可以查看附近的人。
假設使用以下的實作思路,我們透過wx.getLocation()取得使用者目前位置,然後透過wx.request()請求後端資料。但在此之前需要登錄,參考先前官方文件推薦的登入方式,先呼叫wx.login()取得code,再用wx.request()請求開發者伺服器,成功回傳自訂登入態(一般為access_token或其他令牌形式),之後再用自訂登入態請求業務資料。
為了方便看,我把官方文件裡的登入流程貼出來⬇️
思路確定後,開始嘗試coding(以下程式碼不建議看完)
/* 以下为Page对象的方法 */ getNearby: function() { // 判断是否已认证,可采用wx.checkSession()方案 if (isAuth) { // TODO: 获取业务数据 return } // wx.login获取code wx.login({ success(res) { if (res.code) { // 获取自定义登录态 wx.request({ url, method, headers, data, success(res) { // 请求成功 if (res.statuCode === 200) { // 读取响应体中的自定义登录态 let token = res.data.token // 保存自定义登录态 wx.setStorageSync("assess_token", token) // 获取位置信息 wx.getLocation({ success(res) { let { latitude, longitude } = res // 请求业务数据 wx.request({ url, method, header, data: { latitude, longitude }, success(res) { // 请求成功 if (res.statuCode === 200) { let data = res.data // 数据渲染到V层 this.setData({ list: data }) } // 请求失败 else if (res.statuCode === 400) { // TODO } // 其他错误情况状态码处理 // TODO }, fail(err) { // 调用失败处理 } }) }, fail(err) { // 调用失败处理 } }) } // 请求失败 else if (res.statuCode == 400) { // TODO } // 其他错误情况的状态码处理 }, fail(err) { // 调用失败处理 } }) } else { // TODO // 登录失败 } }, fail(err) { // wx.login()调用失败处理 // TODO: ... } }) }
回呼地獄出現了。氣功波代碼,別說別人,連自己看都會覺得噁心。
某天英明的產品經理站了出來,說我們可以加點XXXXX,你可能還得找個地方嵌套其他微信接口或者多加幾個if else
分支,到時候就找個地方哭吧。
解決方案
從某種意義上來說,當今風暴式的前端生態,仰仗於Node以及ES6 的出現。
ES6後對於非同步有多種解決方案。一種是採用generator/yield
,但generator
函數使用起來其實比較麻煩。另外一種是採用Promise
,相對比較簡單。 ES7也可以採用async/await,
但本質上async/awai
t也是基於Promise
。以下介紹Promise
。
Promise
Promise建立
建立Promise很簡單,Promise本身就是一個建構子。透過new創建。建構函數的參數為一個回呼函數,回呼函數有兩個參數為resolve和reject(無需手動維護)。 resolve和reject是用來改變狀態。關於狀態放到後邊講。
// Promise实例的创建 let p = new Promise((resolve, reject) => { // TODO })
Promise有個缺點,一旦創建便會立刻執行。所以一般會用一個函數來包裝。
let getPromise = () => { return new Promise((resolve, reject) => { // TODO }) }
Promise狀態
Promise實例有三種狀態,pending
、resolved
和rejected
,Promise
實例建立後就會處於pending
狀態。回呼函數中的resolve
和reject
就是用來改變Promise
實例狀態的。當呼叫resolve
時,Promise
實例會從pending
變成resolved
狀態,表示成功。當呼叫reject
時,Promise
實例會從pending
變成rejected
狀態,表示失敗。
let getPromise = () => { return new Promise((resolve, reject) => { // TODO // 处理结果 if (result) { resolve(successObject) } else { reject(error) } }) }
常用方法
最常用的方法為then
()和catch
()這兩個方法,透過then
()的傳遞效用就可以解決回調地獄的問題。
其中then
()可接收兩個參數,都是回呼函數,第一個回呼函數用來處理resolved
狀態,參數為Promise
實例呼叫resolve傳遞的成功物件。第二回呼函數用來處理rejected
狀態,參數為呼叫Promise
實例呼叫reject
傳遞的錯誤物件。
實際中then()
我們一般只用來處理resolved的情況,也就是只傳遞第一個回呼函數。對於rejected
情況較多是採用catch
()統一處理。
let getPromise = () => { return new Promise((resolve, reject) => { // TODO // 处理结果 if (result) { resolve(successObject) } else { reject(error) } }) } getPromise() .then(res => { console.log(res) // TODO }) .catch(err => { //TODO })
使用then()
方法可以繼續返回一個Promise
對象,透過return
一個新的Promise
,可以持續的向下傳遞。
getPromise() .then(res => { //第一层Promise console.log(res) // TODO return getPromise() ) .then(res => { // 第二层Promise console.log(res) // TODO }) .catch(err => { // TODO })
其他常用方法有諸如Promise.all()
,Promise.race()
。當需要等待多個Promise
結果時會採用。兩個方法都是接收一個由Promise
組成的物件陣列。使用Promise.all()
時,只有當全部的Promise
物件全部resolved Promise.all()
狀態才是resolved
#。而Promise.race()只需有一個Promise
物件為resolved
時,其狀態就為resolved。
更多方法可閱讀相關文件。
封裝小程式介面
學習了Promise基礎,透過封裝非同步操作,使用Promise鏈就可以解決回呼地獄問題。
因為wx.request()使用頻率比較高,先對wx.request()封裝。
/* 可以将公用的方法挂在app.js中 */ request: function(method, url, header, data) { return new Promise((resolve, reject) => { wx.request({ method, url, header, data, success(res) { resolve(res) }, fail(err) { reject(err) } }) }) }
基本框架就这样,我们可以进一步修改,比如请求url的基础路径,添加一些公用的header,针对状态码做一些全局处理等。
request: function(method, url, header = {}, data = {}) { // 启动时可将storage中的令牌挂到app.js let token = app.assess_token if (token) { header["Authorization"] = token } return new Promise((resolve, reject) => { wx.request({ method, url: "https://api.domain.com/v1" + url, header, data, success(res) { // 请求成功 if (res.statusCode === 200) { resolve(res) } // 请求成功无响应体 else if (res.statusCode === 204) { /* 可做一些成功提示, 如调用wx.showToast()、wx.showModal()或自定义弹出层等 */ resolve(res) } // 未认证 else if (res.statusCode === 401) { /* 可做一些错误提示,或者直接跳转至登录页面等 */ reject(res) } else if (res.statusCode == 400) { /* 可做一些错误提示*/ reject(res) } else if (res.statuCode === 403) { /* 无权限错误提示*/ reject(res) } // ...其他状态码处理 }, fail(err) { /* 可做一些全局错误提示,如网络错误等 */ reject(err) } }) }) }
封装之后,举个例子,发送请求就可以修改为
/* 方法体中 */ let app = getApp() app.request("POST", "/auth", {}, { username, password }) .then(res => { // 第一层请求 // TODO 成功处理 return app.request("GET", "/goods", {}, {}) }) .then(res => { // 第二层请求 // TODO 成功处理 // 渲染视图 }) .catch(err => { // TODO 错误处理 })
封装一下其他的微信接口
/* 可以将公用的方法挂在app.js中 */ wxLogin: function() { return new Promise((resovle, reject) => { wx.login({ success(res) { if (res.code) { resovle(res) } else { reject({ message: "登录失败" }) } }, fail(err) { reject(err) } }) }) } getLocation: function() { return new Promise((resolve, reject) => { wx.getLocation({ success(res) { resolve(res) }, fail(err) { reject(err) } }) }) }
对于最初的例子,可以就修改为
/* Page对象的方法 */ getNearby: function() { // 判断是否已认证,可采用wx.checkSession()方案 if (isAuth) { // TODO: 获取业务数据 return } app.wxLogin() .then(res => { // 将code发送给开发者服务器,获取自定义登录态 return app.request("POST", "/auth", {}, { code, res.code }) }) .then(res => { // 保存自定义登录态 setStorage("access_token", res.data.access_token) // TODO: 其他登录成功操作... return app.getLocation() }) .then(({ latitude, longitude }) => { let url = "/nearby?latitude=" + latitude + "&longitude=" + longitude return app.request("GET", url) }) .then(res => { // TODO: 数据处理 let data = res.data // 渲染视图层 this.setData({ data }) }) .catch(err => { // TODO 错误处理 }) }
之后若有需添加新的请求或者其他异步操作,直接在Promise链上操作就行了。
推荐教程:《微信小程序》
以上是Promise實務 實作微信小程式介面封裝的詳細內容。更多資訊請關注PHP中文網其他相關文章!