前言
在JS运行环境中,使用回调函数,来异步编程,会遇到“回调地狱”的问题。为了解决“回调地狱”的问题,我在前面小程序wx.request的封装一文中用到了Promise。 本文则进一步解析异步编程中的Promise的使用,有错误之处,还请指正。
问题
我们先看看什么是回调地狱:
某个异步操作:
fetchGroups(url,success: (res) => {
fetchActivitiesByGroupId(res.id, success:(res) {
fetchActivityRegistered(res.activityId, success:(res)) {
console.log("this activity is registried).
}
})
})
异步获取群组,并传入成功时回调函数; 当回调函数被调用时,在内部再调用获取活动列表并传入一个回调。可见随着嵌套的回调函数越多,代码越加难阅读和维护。
那么有什么办法? Promise则提出了新的想法:把异步操作以看起来像同步的代码写出来。
具体做法:把异步操作的结果保存到promise对象中, 监听异步操作的回调函数不是作为参数一开始就传入异步函数,而是把它们绑定到promise对象上。代码如下所示:
var promise = fetchGroups(url)
var promise2 = promise.then(success: (res)=> fetchActivitiesByGroupId(res.id) })
var promise3 = promise2.then(success: (res) => fetchActivityRegistered(res.id) })
promise3.then((res)=>{ console.log("this activity is registried") })
或者写成链式调用,更简洁:
fetchGroups(url)
.then(res => fetchActivitiesByGroupId(res.id) })
.then(res => fetchActivityRegistered(res.id) })
.then(res => { console.log("this activity is registried") })
在上面 fetchGroups(url)调用后,添加的第一个then()上绑定的回调函数也返回了一个promise对象,所以我们可以继续在其后继续添加then(),从而形成链式调用。上面绑定的回调函数会按照then方法添加的顺序执行。
异常处理
异常处理分两种情况:
1.Catch()后可以继续链式操作。
异常捕获后,还2可以通过then(),在catch之后,继续绑定新的回调函数,程序能继续执行。
fetchGroups(url)
.then(res => fetchActivitiesByGroupId(res.id)})
.then(res => fetchActivityRegistered(res.id)})
.then(res => {
throw error("got a exception")
console.log("this activity is registried")
})
.catch(err => {console.log("catch an error.")}
.then(res => {console.log("I need to update ui, finally.")}
上面代码日志会输出:
line0: "catch an error."
line1: "I need to update ui, finally."
可见,如果在catch之后,继续在回调链上添加函数,依然会得到执行,类似异常处理中的finally代码块的作用。
2.异常的传播链
如果遇到异常被抛出,浏览器/小程序框架会沿着链,找到第一个catch指定的回调函数执行。
fetchGroups(url)
.then(res => {
throw error("got a exception")
return fetchActivitiesByGroupId(res.id)
})
.then(res => fetchActivityRegistered(res.id)})
.then(res => {
console.log("this activity is registried")
})
.catch(err => {console.log("catch an error.")}
then()遇到异常抛出,则会顺着调用链,查到第一个catch函数,把异常捕获。
输出结果:
"catch an error."
封装旧API
通过创建Promise,我们可以封装旧的api,如封装小程序中的wx.request,使得网络请求支持链式调用:
/**
* 以HTTP method == GET的方式,发起网络请求。
* @param {对象参数:可以把多个参数封装到一个对象,作为参数传入函数} params
*/
const get = (params) => {
var _token = getApp().getToken()
return new Promise((resolve, reject) => {
wx.request({
method:'GET',
url: concatUrl(params),
header:{
'Authorization': _token,
'content-type': 'application/json',
},
success: (res) => {
console.log(res)
checkAuth(res) // 在此处统一拦截token过期,跳转到登录界面
resolve(res)
},
fail:(err) => {
console.log("请求失败" + err)
reject(err)
}
})
})
}
Promise常见API
Promise.all() :把多个异步操作组合起来,并发多个操作,等待结果全部返回后,再处理它们。
Promise.all[fun1(),fun2(),fun3()].then(([result1,result2,result3]) => { ... })
总结
本文通过对Promise的使用方法总结,可以理解Promise是如何解决回调地狱的问题,以及它的异常传播链和在抛出异常后继续添加绑定函数,在末尾讲了如何创建Promise来封装旧的API,使得旧API也支持链式调用,最后简单介绍了Promise.all()并发处理多个异步操作并等待它们的结果。
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises