1、同步与异步
在小程序中网络请求wx.request不像其他框架可以设置同步或异步,wx.request在小程序中只能是异步方式。
既然是异步方式,就不能用以下的方式获取网络数据:
let data = wx.request({
url:'',
header:{
appket:""
}
})
而应该使用回调函数来获取请求后的数据。
wx.request({
url:'',
header:{
appket:""
},
success:function(res){
console.log(res)
}
})
2、箭头函数
如果回调函数使用传统写法,即上述写法,在使用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)
}
})
3、回调函数的嵌套
如果在一个函数里要调用一个异步函数,则一定要把一个回调函数作为该函数的参数。
比如一个函数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
})
})
4、Promise与异步
实现异步的三种方式:
(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是一个对象,不是函数,对象可以保存状态,而函数不行。
5、使用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状态,该方法返回,进行后面的处理。