相信很多开发者都遇到过回调地狱的问题。由于微信小程序的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/await也是基于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实践 实现微信小程序接口封装的详细内容。