前记:本篇文章不是基础知识科普篇,而是实际工作中的记录篇,以实际工作需求带出前端请求处理的演变历程。
开篇先来一个回调callback的例子(来自微信小程序 Api):
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
}
})
赤裸裸的success回调,设想一下如果我request里面嵌request甚至再嵌好几个~~~那画面就叫回调炼狱。
改造成promise形态,学习最简单的promise方法封装
再不改造更待何时,第一个浮现在脑海的东西自然是promise,我们先来改造request,当然我改造了不少小程序原有的api。我给这些原有api都采用了promise化的赋能。在wx方法里面独立出来了wx.top库。于是我的request变成了wx.top.request,使用参数不变。
wx.top = {};
wx.top.request = function(){
return new Promise((resolve, reject){
wx.request({
url: args.url, //仅为示例,并非真实的接口地址
data: args.data,
//header:这里你按需定制比如全局token
success (res) {
//这里也可以根据后端通讯规范,设置自己的relove和reject
relove(res);
},
fail (res){
reject(res);
}
});
});
}
此时的request数据返回怎么处理
wx.top.request({
url : "xxx",
data : {}
}).then(res=>{
console.log(res); //请求的返回信息
});
以上是promise最直接的用法,封装方式就是return new Promise对象即可。Promise化的方法,你只需要关注两点,什么时候resolve什么时候reject。
在我们项目上线后的一个月,突然有一天我接到一个工单,说是直接下单不能买了,跟后端反复查实,最后找到了问题,原来是下单数据依赖的接口请求有好几处(拉取地址信息,计算运费,计算个人账户余额),很容易出现还没算好运费就被用户点了购买的情况,我试图去用三个标记去判断可以提交,拦截是可以,代码可读性不好,然后又没办法做友好度提示。于是我又做了如下改进:
利用promise封装原子方法,利用promise实现接口以来,利用promise实现统一状态管理
一、计算运费方法:
calcPostFee(){ //获取运费
return new Promise((resolve, reject) => {
if(isPost || !address){ //如果包邮或者没有地址信息返回0
resolve(0);
}else{
orderFreight(address, order).then(res => { //order
//这里resolve(运费)或者reject(错误信息)
}).catch((err) => {
console.log(err); //接收错误信息-信息包含请求错误|代码错误
wx.top.showTip(getObjVal(err, ['data', 'msg'],true) || '获取运费失败'); //getObjVal是我自己封装的js对象数据方法
reject(err);
});
}
});
},
二、获取地址信息:
queryAddress(){ //获取运费
return new Promise((resolve, reject) => {
queryUserAllAddress({ header : {token : this.userInfo.token} }).then(res=>{
//这里resolve(地址信息)或者reject(错误信息);
}).catch((err)=>{
console.log(err);
wx.top.showTip(getObjVal(err, ['data', 'msg'],true) || '获取地址失败');
reject(err);
});
});
},
三、计算个人余额及订单费用方法:
calcOrder(order){ //计算余额及订单费用
return new Promise((resolve, reject) => {
//单纯的计算没有任何请求,这里我也想提醒大家一下promise是个工具,不是只有请求才可以用,万物皆可promise
//这里resolve(正确信息)或者reject(错误信息);
});
},
四、接口总统领:
prepareData(){
return new Promise((resolve, reject)=>{
wx.showLoading({title : "订单正在生成...", mask : true}); //开启mask,不可点击。可以统一提示了,代码再也不散乱了
this.queryAddress().then(()=>{ //地址信息回来之后
return this.calcPostFee(); //再计算运费
}).then(()=>{
return this.calcOrder(); //算好订单数据之后
}).then(()=>{
reeolve();
wx.hideLoading();//解开遮罩,避免误点
}).catch((err)=>{
wx.hideLoading(); //隐藏loading
});
});
}
prepareData.then(res=>{
this.setData({ disableBtn : false; }); //可以去购买了
}).catch(()=>{
this.setData({ disableBtn : true; });//设置购买按钮不可点击
})
下面我开始推出async await
这是https://javascript.info/async-await中的一句话,概括的很到位,
说async await是让promise以更舒适更时髦的方式工作的语法,而且其极度容易理解和使用
先上个例子来让大家走进基本语法:
async prepareData(){
wx.showLoading({title : "订单正在生成...", mask : true});
try{
let address = await this.queryAddress();
let fee = await this.calcPostFee();
let amt = await this.calcOrder();
wx.hideLoading();
return true;
}catch(e){//任意一个方法错误(reject或者js错误)都可以被捕获
wx.hideLoading();
}
}
prepareData().then(...)
嗯,不错确实优雅了很多,也洋气了,await用的很传神。就是捕获错误的方式有点不优雅,好的,上另外一种方式:
async prepareData(){
let address = await this.queryAddress();
let fee = await this.calcPostFee();
let amt = await this.calcOrder();
}
//使用
wx.showLoading({title : "订单正在生成...", mask : true});
prepareData().then(...).catch(...);
咿?这里的prepareData也可以then跟catch?
没错儿,async 修饰词就是让方法体变成了Promise对象,等同于在方法体内部return new Promise。
而await只能在async修饰的方法体内部使用,作用也很明确,我去买橘子,你等着。
很多人说async await方法是回调的终极解决方案,我不认同,我认为其只是一种方法载体,让Promise的写法更优雅。 请看如下实验,fa方法执行时间很明显比fb执行时间要长,但是await告诉fb你先等着,请看下例:
验证一下await的执行顺序
function fa(){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('a')
},2000);
});
}
function fb(a){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
console.log(a);
resolve('b'+a)
},1000);
});
}
async function c(){
let a = await fa();
let b = await fb(a);
}
c().then(res=>{
console.log(res);
});
反思Promise有all跟race方法,怎么用async跟await去实现这样的机制呢?也许有方式,但是我想说的还是它只是Promise的载体,跟最终的解决方案范畴有点出入。不管js以什么样的方式什么样的速度发展,我希望能够出来一些解决真正业务痛点的语法,谢谢各位观看者。
网友评论