相信很多开发者都遇到过回调地狱的问题。由于微信小程序的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实践 实现微信小程序接口封装的详细内容,更多请关注天达云其它相关文章!