个人技术总结 —— 微信小程序异步请求Promise
| 这个作业属于哪个课程 | 2021春软件工程实践 S班 |
| :------------------- | :----------------------------------------------------------- | ---------------------------------------------------- |
| 这个作业要求在哪里 | 软件工程实践总结&个人技术博客 | |
| 这个作业的目标 | 对软工实践过程中技术的总结 | |
| 其他参考文献 | 见文末 | |
技术概述
- 介绍:Promise用于处理小程序中的异步问题
- 学习该技术原因:项目中很多地方需要用到异步操作,例如:只有当用户登录之后才能去访问其它接口,不然无法访问;我们为图片上传定义了单独的接口,当需要发布文章是,需要等所有图片上传之后得到图片的Url再调用发布文章的接口……
- 技术难点: 提取项目中的异步关系
技术详述
介绍
- Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它属于ECMAScript6中新加入的内容,微信对其进行了封装处理。
- Promise特点如下:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- Promise的缺点在于一旦某一个进程被创建执行便无法停止,只能待其执行完毕。
promise在小程序中的使用框架
1、封装函数
function myPromise(func) {
return function (obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function (res) {
resolve(res)
}
obj.fail = function (res) {
reject(res)
}
func(obj) //执行函数,obj为传入函数的参数
})
}
}
2、调用过程
var promise = require('./utils.js');
//引入封装好的自定义的myPromise函数
promisw.myPromise(preFunc)
.then(afterFunc)
.catch(falutFunc);
//只有当preFunc执行完成后才会执行afterFunc函数
3、将preFunc中的值传递到afterFunc回调函数中(引用菜鸟教程中的代码)
function ajax(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var URL = "/try/ajax/testpromise.php";
ajax(URL).then(function onFulfilled(value){
document.write('内容是:' + value);
}).catch(function onRejected(error){
document.write('错误:' + error);
});
- resolve和reject都可用于参数的传递,resolve在promise成功时使用,将参数传递至promese.then中定义的函数,而reject则传递至promise.catch中定义的函数。
4、promise.all方法
- var p = Promise.all([p1,p2,p3]);
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
5、promise.race
- var p = Promise.race([p1,p2,p3]);
- 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
问题与解决过程
1、登录之后才能调用某些接口,否则后端无法获取正在访问的用户的信息,自然无法返回相应的前端想要的数据
解决方案
- 将封装Login,并在其中返回一个Promise对象
- 做法为先封装userLogin函数,userLogin函数返回一个Promise对象,在获取十大热帖信息函数中通过userInfo全局变量去判断该用户是否有登录,若尚未登录则调用userLogin.then,否则直接getTopTen获取信息就可
userLogin:function (e) {
let that=this;
let promise=new Promise(function (resolve,reject) {
let _that = that;
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
console.log(res.code);
console.log("登录" + that.globalData.baseUrl);
request({
url: that.globalData.baseUrl + '/api/user/login',
method: 'POST',
data:
res.code,
success:function(respond)
{
that.globalData.userInfo = respond.data.data;
console.log("登录成功");
console.log(respond);
that.globalData.userId = respond.data.data.id;
resolve(respond);
},
fail:function(respond)
{
// this.globalData.userInfo=res.data;
console.log("登录失败");
console.log(respond);
reject(respond);
}
})
}
})
})
return promise;
},
- 在需要登陆后才能访问的函数调用放在userLogin.then()中
例如:项目中的获取十大热帖信息
//定义获取十大热帖信息的函数,调用相应接口
getMes: function(){
let topTen = this.data.topTen;
let that = this;
let baseUrl = app.globalData.baseUrl;
request({
url: baseUrl + '/api/posts/heatposts',
method:'POST',
data: {
userId: 1
},
success:function(res)
{
// console.log(res);
let resData = res.data.data;
if(resData != null){
for(let i = 0; i < resData.length; i++){
if(resData[i].message.length > 40)
resData[i].message = resData[i].message.substr(0, 40) + "...";
topTen.push(resData[i].message);
}
}
that.setData({
topTen
})
},
fail:function(res)
{
console.log(res);
}
});
this.setData({
topTen
});
}
//在这里使用promise
getTopTen: function(){
if(!app.globalData.userInfo){ //尚未登录
userLogin
.then(this.getMes())
.catch(……)
}
else //已登录
this.getMes();
}
2、发布组局图片上传问题
- 在项目中,我们为上传图片单独定义了一个接口,使用该接口上传图片后会返回刚才上传的图片在服务器中的存放位置。
- 在发布组局中,上传图片成功后返回的文件名是需要当作发布贴文接口的参数一并发起请求。所以必须等全部图片上传成功后再调用发布组局的接口。
- 应为一个组局会有多张图片的情况发生,所以这里需要使用promise.all
let promiseArr = []; //一张图片一个promise,存放所有promise的数组
let imgServerUrls = new Array();
// console.log(_this.data.fileList);
file.forEach(function (e) {
var FSM = wx.getFileSystemManager();
let imageType = getApp().getImageType(e.url);
promiseArr.push(
new Promise(function (resolve, reject) {
FSM.readFile({
filePath: e.url,
encoding: "base64",
success: function (data) {
wx.request({
url: app.globalData.baseUrl + '/api/posts/imgupload',
method: "POST",
data: {
base64Str: imageType + data.data,
filename: "111"
},
success: function (res) {
console.log(res);
console.log("上传图片成功");
if (res.data.code == 200) {
return resolve(res);
}
else {
return reject(res.data.message);
}
}, //promise.all当所有图片上传成功后才会调用
fail: function (e) {
console.log(e);
console.log("上传图片失败\n" + res.data.message);
return reject(e)
},
complete: function (complete) {
return complete;
}
})
}
});
})
)
})
Promise.all(promiseArr).then(function (values) {
//调用发布组局的接口
)
}
}
我的总结
- promise主要是为了解决前端异步访问的问题,在本次实践中我只用到了最基本的用法,以及promise.all().
- 上图是官方文档给出的所有用法的一个列表,自己还需要进一步加强学习
- 此外,promise的嵌套使用能够简化代码结构,但在写这部分代码之前,最好将代码的逻辑结构通过流程图的方式画出来,不然容易把自己嵌套晕😵。