我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理。比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环操作),则页面会产生卡死的现象。
所以,在JavaScript中,提供了一些异步特性,为程序提供了性能和体验上的益处,比如可以将代码放到setTimeout()中执行;或者在网页中,我们使用Ajax的方式向服务器端做异步数据请求。这些异步的代码不会阻塞当前的界面主进程,界面还是可以灵活的进行操作,等到异步代码执行完成,再做相应的处理。
一段典型的异步代码类似这样:
function asyncFunc(callback) {
setTimeout(function () {
//在这里写你的逻辑代码
//...
//逻辑代码结束,执行一个回调函数
callback();
}, 5000);
}
或者:
function getAccountInfo(callback, errorCallback) {
wx.request({
url: '/accounts/12345',
success: function (res) {
//...
callback(data);
},
fail: function (res) {
//...
errorCallback(data);
}
});
}
然后我们这样调用:
asyncFunc(function () {
console.log("asyncFunc() run complete");
});
getAccountInfo(function (data) {
console.log("get account info successfully:", data);
}, function () {
console.error("get account info failed");
});
这是一种使用了回调函数来控制代码执行流程的方式。这样看起来没问题,也挺容易理解。
但是,如果我们一段代码中,异步操作太多,又要保证这些异步操作是有顺序的执行,那我们的代码就看起来非常糟糕,就像这样:
asyncFunc1(function(){
//...
asyncFunc2(function(){
//...
asyncFunc3(function(){
//...
asyncFunc4(function(){
//...
asyncFunc5(function(){
//...
});
});
});
});
});
这样的代码可读性和可维护性可想而知了。还有,回调函数真正的问题在于:
它剥夺了我们使用 return 和 throw 这些关键字的能力。
那有什么办法来改善这个问题呢?答案是肯定的,Promise这种概念的产生,很好地解决了这一切。关于什么是Promise,一搜一大把介绍,我这里就不复制粘贴了,我主要是讲一下我们怎么用它来解决我们的问题。
我们来看一下,上面的例子如果使用Promise,它会是什么样子?我们先将这些函数变成Promise的方式:
function asyncFunc1(){
return new Promise(function (resolve, reject) {
//...
})
}
// asyncFunc2,3,4,5也实现成跟asyncFunc1一样的方式...
然后看一下他们是怎么样被调用的:
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.then(asyncFunc4)
.then(asyncFunc5);
这样,这些异步函数就会按照顺序一个一个依次执行了。
ES6中原生支持了Promise,不过在原生不支持Promise的环境中,我们有很多第三方库来支持,比如Q.js和Bluebird。它们一般都除了提供标准Promise的API外,还提供了一些标准之外但非常有用的API,使得异步流程的控制更加优雅。
从微信小程序的API文档中我们可以看到,框架提供的JavaScript API中很多函数其实都是异步的,如wx.setStorage(), wx.getStorage(), wx.getLocation()等等,它们也是提供的回调的处理方式,在参数中传入success, fail,complete回调函数,就可以对运行成功或失败进行分别处理。
如:
wx.getLocation({
type: 'wgs84',
success: function(res) {
var latitude = res.latitude
var longitude = res.longitude
var speed = res.speed
var accuracy = res.accuracy
},
fail: function() {
console.error("get location failed")
}
})
我们能不能让微信小程序的异步API支持Promise呢?答案是肯定的,我们当然可以一个一个的用Promise去包装这些API,但是这个还是比较麻烦的。不过,由于小程序的API的参数格式都比较统一,只接受一个object参数,回调都是在这个参数中设置,所以,这为我们的统一处理提供了便利,我们可以写一个非侵入性的工具方法,来完成这样的工作:
假设我们将这个工具方法写到一个名为的util.js的文件中:
var Promise = require('../libs/bluebird.min') //我用了bluebird.js
/**
* 将小程序的API封装成支持Promise的API
* @params fn {Function} 小程序原始API,如wx.login
*/
function wxPromisify(fn) {
return function (obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function (res) {
resolve(res)
}
obj.fail = function (res) {
reject(res)
}
fn(obj)
})
}
}
module.exports = {
wxPromisify: wxPromisify
}
之后,我们来看一下如何使用这个方法,将原来回调方式的API变成Promise的方式:
var util = require('../utils/util')
var getLocationPromisified = util.wxPromisify(wx.getLocation)
getLocationPromisified({
type: 'wgs84'
}).then(function (res) {
var latitude = res.latitude
var longitude = res.longitude
var speed = res.speed
var accuracy = res.accuracy
}).catch(function () {
console.error("get location failed")
})
是不是很容易理解?
关于使用Promise处理异步流程,就先讲到这里,有什么疑问,可以留言给我。不对之处,欢迎指正。
谢谢大家阅读本文。
网友评论
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.then(asyncFunc4)
.then(asyncFunc5);
这里并不会等待每个then体内的异步执行完成后再执行下一个then体;
测试代码如下:
function testPromise(state,num){
return new Promise(function(resolve, reject){
if(state==1)
{
console.log("resolve oninit:"+num);
setTimeout(function(){
resolve();
console.log("resolve:"+num);
},num);
}
else
{
reject();
console.log("reject:"+num);
}
});
}
testPromise(1,0)
.then(testPromise(1,4000))
.then(testPromise(1,2000))
.then(testPromise(1,1000))
//输出:
// test.html:1810 resolve oninit:0
// test.html:1810 resolve oninit:4000
// test.html:1810 resolve oninit:2000
// test.html:1810 resolve oninit:1000
// test.html:1813 resolve:0
// test.html:1813 resolve:1000
// test.html:1813 resolve:2000
// test.html:1813 resolve:4000
这里这个视频的前半个小时有讲解Promise的用法,你可以了解下:https://www.bilibili.com/video/av25143408
我所理解你的意思是在resolve中返回了要继续执行的promise,同理也可以同样给失败reject时指定要继续执行的promise;
相当于:
testPromise(1,0)
.then(function(){return testPromise(0,4000)})
.then(null,function(){return testPromise(1,2000)})
.then(function(){return testPromise(1,1000)}) ;
1.如果说需要在resolve来返回新的promise,那为什么在执行reject()时,不在前面加上 return reject(); 呢?
testPromise(1, 0)
.then(() => testPromise(1, 4000))
.then(() => testPromise(1, 2000))
.then(() => testPromise(1, 1000))
自己再体会一下。
......
getLocationPromisified({
type: 'wgs84'
}).then(function (res) {
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
geocoder = new qq.maps.Geocoder({
complete: function (result) {
var province = result.detail.addressComponents.province
var city = result.detail.addressComponents.city;
var district = result.detail.addressComponents.district
var location = "" + province + city + district;
flag = true;
$("input[name='location']").val(location);
loadProduct(location, productLabel, productBrand, searchKeyword, order, pageNo);
}
});
var coord = new qq.maps.LatLng(Number.parseFloat(latitude), Number.parseFloat(longitude));
geocoder.getAddress(coord);
}).then(function(){
if(!flag) {
loadProduct("", productLabel, productBrand, searchKeyword, order, pageNo);
}
})
为什么先执行了第二个then,然后在执行第一个
onLoad: function (options) {
var getLocationPromisified = cusPromise.wxPromisify(app.onLaunch())
getLocationPromisified().then(function (res) {
console.log(res)
// var latitude = res.latitude
// var longitude = res.longitude
// var speed = res.speed
// var accuracy = res.accuracy
}).catch(function () {
console.error("什么错误")
})
},
这里的:var getLocationPromisified = cusPromise.wxPromisify(FUN)。FUN应该怎样递。谢谢大神
var getUserAutoLogin= util.wxPromisify(app.getUserAutoLogin())
则会直接走到 .catch()
onLoad: function (options) {
var getUserAutoLogin= util.wxPromisify(app.getUserAutoLogin)
console.log(getUserAutoLogin)
}
打印出来的getUserAutoLogin为:
ƒ () {
var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return new Promise(function (resolve, reject) {
obj.success = function (res) {
resolve(re…
此时应该说明已经返回了Promise了吧,然后我下面调用:
getUserAutoLogin().then(function (res) {
console.log(1111)
this.setData({
userInfo:app.globalData
})
}).catch(function () {
console.error("get location failed")
})
但是 打印不出1111,请问是哪里出了问题
这个函数内部其实就是创建一个简单的Promise并在该Promise中执行传入的原生API。在执行过程中,传递给原生API的obj参数中的回调函数success和fail会被我们的覆盖(见代码,调用resolve和reject来导出执行结果),所以在调用这个Promise后的then和catch中,我们就能获取到原生API的执行结果了。
我想在这个wxLogin函数执行完毕后,执行一段代码,我该怎么写呢?
var wxLogin = util.wxPromisify(wx.login)
wxLogin().then(function(res) {
console.log("获取到的code: ", res.code)
// 你的下一端代码逻辑......
}).then(function(){
// 你的另一端代码逻辑......
})
var login = util.wxPromisify(app.login());
var userInfoFun = util.wxPromisify(app.userInfoFun());
var register = util.wxPromisify(app.register());
console.log("login=", login);
login().then(function (res) {
}).then(userInfoFun).then(function (res) {
}).then(register).then(function(res){
})
var login = util.wxPromisify(app.login);
var userInfoFun = util.wxPromisify(app.userInfoFun);
var register = util.wxPromisify(app.register);
仔细体会吧:)
wxRequest({
url: 'https://...........'
}).then(function (res) {
// 处理第一个请求结果...
}).then(function () {
return wxRequest({
url: 'https://...........'
})
}).then(function (res) {
// 处理第二个请求的结果...
})
var fun1 = util.wxPromisify(that.fun1())
var fun2 = util.wxPromisify(that.fun2())
fun1().then(function (res) {
console.log(res)
}).catch(function (res) {
console.log(res)
console.error("get location failed")
})
fun2().then(function (res) {
console.log(res)
}).catch(function (res) {
console.log(res)
console.error("get location failed")
})
fun1().then(function (res) {
console.log(res)
}).then(function () {
return fun2().then(function (res) {
console.log(res)
})
}).catch(function (err) {
console.log(err)
console.error("get location failed")
})
fun1().then(function (res) {
console.log(res)
}).then(fun2).then(function (res) {
console.log(res)
}).catch(function (err) {
console.log(err)
console.error("get location failed")
})
这么写可以生效;
而先promisefy,然后export出去:
function Login() {
return wxPromisify(wx.login);
}
module.exports = {
wxPromise: wxPromisify,
Login: Login
}
在其他地方调用就会报错:
wc.Login(...).then is not a function;
var fun1 = util.wxPromisify(that.fun1())
var fun2 = util.wxPromisify(that.fun2())
fun1().then(function (res) {
console.log(res)
}).catch(function (res) {
console.log(res)
console.error("get location failed")
})
fun2().then(function (res) {
console.log(res)
}).catch(function (res) {
console.log(res)
console.error("get location failed")
})
方式一:调用的时候应该写成:
wc.Login()(...).then(....)
或
var loginPromisified = wc.Login()
loginPromisified(...).then(...)
方式二:把function Login的定义改一下,改成:
function Login(params) {
return wxPromisify(wx.login)(params)
}
然后调用的时候可以:wc.Login(...).then(...)
http://www.jianshu.com/p/d9a0202bd257