在小程序中网络请求wx.request不像其他框架可以设置同步或异步,wx.request在小程序中只能是异步方式。 既然是异步方式,就不能用以下的方式获取网络数据:
let data = wx.request({ url:'', header:{ appket:"" } })而应该使用回调函数来获取请求后的数据。
wx.request({ url:'', header:{ appket:"" }, success:function(res){ console.log(res) } })如果回调函数使用传统写法,即上述写法,在使用data里的数据是,this.data中的this指的并不是Page对象,所以这样无法获取data数据。通常做法是在回调函数外部用that来存储this:
let that = this; wx.request({ url:'', header:{ appket:"" }, success:function(res){ console.log(that.data.test) } })而使用箭头函数,则可以不需要that来存储this,直接使用this就行:
wx.request({ url:'', header:{ appket:"" }, success:(res) => { console.log(this.data.test) } })如果在一个函数里要调用一个异步函数,则一定要把一个回调函数作为该函数的参数。 比如一个函数getLatest()要根据index从服务器获取相应的latest对象,则应该把获取数据的操作放在回调里,然后把这个回调作为getLatest()的一个参数。 getLatest()函数定义:
getLatest(sCallback) { this.request({ url: 'classic/latest', success: (data) => { // 如果不用箭头函数,this将指代不正确 let key = this._fullKey(data.index) wx.setStorageSync(key, data) this._setLatestIndex(data.index) sCallback(data) } }) }其中request函数也是异步函数,因此sCallback要放在request函数的回调中。 request函数定义如下:
request(params) { var that = this var url = this.baseRestUrl + params.url; if (!params.method) { params.method = 'GET'; } wx.request({ url: url, data: params.data, method: params.method, header: { 'content-type': 'application/json', 'appkey':config.appkey }, success: function (res) { // 判断以2(2xx)开头的状态码为正确 // 异常不要返回到回调中,就在request中处理,记录日志并showToast一个统一的错误即可 var code = res.statusCode.toString(); var startChar = code.charAt(0); if (startChar == '2') { //先判断params.success是否为空, //如果不为空,将res.data作为参数传入params.success params.success && params.success(res.data); } else { params.error && params.error(res); } }, fail: function (err) { params.fail && params.fail(err) } }); }getLatest()函数调用:
getLatest((data)=>{ this._getLikeStatus(data.id, data.type) this.setData({ classic:data }) })该回调函数是用箭头函数写的。
总之,存在异步函数的嵌套时,外层函数的返回值或数据处理要放在内层函数的回调里,以此类推。
而使用Promise时就可以解决回调嵌套的问题,因为Promise保留了函数return的功能。
比如requset()是个异步函数,而getHotList()中调用了requset(),页面中又执行了getHotList()函数。因此用回调函数的写法应该有两层嵌套。下面用Promise实现:
request()函数:
request({url,data={},method='GET'}){ return new Promise((resolve, reject)=>{ this._request(url,resolve,reject,data, method) }) } _request(url,resolve, reject, data={}, method='GET'){ wx.request({ url:config.api_base_url + url, method:method, data:data, header:{ 'content-type':'application/json', 'appkey':config.appkey }, success:(res)=>{ const code = res.statusCode.toString() if (code.startsWith('2')){ resolve(res.data) } else{ reject() const error_code = res.data.error_code this._show_error(error_code) } }, fail:(err)=>{ reject() this._show_error(1) } }) }getHotList()调用request()
getHotList() { return this.request({ url: 'book/hot_list' }) }调用getHotList()函数:
bookModel.getHotList() .then(res => { this.setData({ books:res }) })实现异步的三种方式: (1)纯粹callback; (2)Promise; (3)async与await(ES2017)
目前来说,由于小程序暂时不支持async与await,所以Promise是小程序处理异步的最佳解决方案。即使支持async,它也只是Promise的语法糖,所以Promise是必须要学习的基础。
Promise相对于回调函数的优势: (1)解决了纯粹callback嵌套造成的回调地狱问题; 如果在success回调函数中再次进行异步操作,而在该异步操作的回调函数中再进行异步操作,就形成了异步嵌套,会使代码的可阅读性变得很差,造成“回调地狱”:
wx.request{ url:'', header:{ appket:"" }, success:(res) => { wx.request({ success:(res) => { wx.request({ success:(res) => { } } }) } })当然如果只有一次回调,就没必要用Promise了。
let promise = new Promise((resolve, reject)) => { wx.request{ url:'', header:{ appket:"" }, success:(res) => { wx.request({ success:(res) => { wx.request({ success:(res) => { } } }) } }) promise.then((res) => { console.log(res) }) }(2)解决了回调函数剥夺函数return能力的问题;
通常异步函数中是不能return结果的,而Promise可以解决这个问题。 (3)使代码更具可读性; (4)实现多个异步等待合并;
Promise是一个对象,不是函数,对象可以保存状态,而函数不行。
参考Javascript:Promise对象基础 (1)构造Promise Promise构造器接受一个函数作为参数,这个函数有两个参数:resolve,reject,分别代表这个Promise实例成功之后的回调函数和失败之后的回调函数。 这里我们将一个异步函数getSystemInfo()作为Promise的参数。
const promise = new Promise((resolve, reject) => { wx.getSystemInfo({ success: (res) => { resolve(res) }, fail: (error) => { reject(error) } }) } })(2)Promise 的状态 Promise有3种状态:
Pending:进行中Resolved(Fulfilled):已完成Rejected:已失败Promise状态的改变只有两种:
Pending --> Resolved Pending --> Rejected 这意味着,一个Promise对象resolve之后,状态就一直停留在Resolved那里了,反过来reject也一样。 这种特点的结果是,Promise对象的状态改变之后,你再给它添加回调函数,这个函数也会执行。 这跟事件监听器很不一样 —— 你一旦错过某个事件,就没办法再捕获他了,除非这个事件再次发生。 (3).then() 和 .catch() .then() 接收两个回调函数作为参数,第一个是当promise变成成功状态的回调函数;第二个是当promise变成失败状态的回调函数。
const promise = new Promise((resolve, reject) => { wx.getSystemInfo({ success: (res) => { resolve(res) }, fail: (error) => { reject(error) } }) }) promise.then( (res) => { console.log(res) },(error) => { console.log(error) })箭头函数简写: Promise的精髓: Promise作为对象保存了调用异步函数的结果,不需要附带任何回调函数。什么时候需要取Promise中的异步结果时,才使用.then() 和 .catch()调用一步函数。
下面针对一个获取服务器数据的request()方法分别用回调形式和Promise来写: 当需要在其他函数中调用该request方法时: (4)promise实现链式调用 不管是then方法还是catch方法返回的都是一个新的Promise实例,这意味着Promise可以链式调用then和catch,每一个方法的返回值作为下一个方法的参数。
下面要实现多次调用API,即链式调用API,分别是错误的和正确的Promise用法: 嵌套式的写法又跟回调函数的写法一样了,不能体现Promise的作用。而应该把.then()写在外面,下面的.then()会接收上面的.then()的结果并作为参数继续执行。 (5)Promise.all() 和 Promise.race()
Promise.all()接收一个Promise对象的数组作为参数,当这个数组里的所有Promise对象全部变为resolve的时候,该方法才resolve。 如果其中一个Promise对象为reject的话,则该方法为reject。
比如有三个异步操作,都是向服务器请求数据,返回的Promise对象分别是detail ,comments ,likeStatus 。 开始获取数据前显示loading,获取完隐藏loading,这就需要使用Promise.all() 方法:
wx.showLoading() const bid = options.bid const detail = bookModel.getDetail(bid) const comments = bookModel.getComments(bid) const likeStatus = bookModel.getLikeStatus(bid) Promise.all([detail, comments, likeStatus]) .then(res => {//res是一个数组 this.setData({ book: res[0], comments: res[1].comments, likeStatus: res[2].like_status, likeCount: res[2].fav_nums }) wx.hideLoading() }) Promise.race()使用方法和Promise.all一样,接收一个Promise对象数组为参数。 只要其中一个Promise对象变为Resolved或者Rejected状态,该方法返回,进行后面的处理。