1 回调地狱
回调地狱已经是老生常谈的话题了;它出现在异步请求的需求中,即Ajax之类的请求;
举例如果有20个ajax请求;每一个都依赖于前一个的请求结果(即后一个接口的请求参数需要从前一个的结果去拿),那么必须等前一个请求结果结束,然后在回调中,开始调用第二个;第三个请求在第二个的回调中调用,依次进行,将有20个嵌套回调,所以这叫回调地狱,比18次地狱更恐怖~
下面例子都可直接在浏览器直接运行;替换接口即可
如下例子:用浏览器原生的fetch请求接口,结果需要用固定的response.json()处理成js对象;
<!doctype HTML>
<html>
<head>
<meta charset="utf-8"></meta>
<style>
html,body{
width:100%;
height:100%;
margin: 0;
}
</style>
</head>
<body>
<script>
// 连续的回调请求
fetch('http://localhost:8088/api/getinfo1').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
// 第一层回调
console.log(res.data.server);
fetch('http://localhost:8088/api/getinfo2').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
// 第一层回调
console.log(res.data.server);
fetch('http://localhost:8088/api/getinfo3').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
// 第三层回调
console.log(res.data.server);
// ....
// ...第100层回调
// ...地狱
}
})
}
})
}
})
</script>
</body>
</html>
如果说永远只有10个,20个未尝不可;毕竟像一套流水线工程,方便管理;但是如果成百上千那简直是噩梦;
如何解决回调地狱?
目前我们在项目中常用的方式是ES7的async 和await;使用他们可以解决嵌套回调问题,像用同步代码一样按顺序执行即可;如下
<!doctype HTML>
<html>
<head>
<meta charset="utf-8"></meta>
<style>
html,body{
width:100%;
height:100%;
margin: 0;
}
</style>
</head>
<body>
<script>
init()
async function init(){
await geoInfo1();
await geoInfo2();
await geoInfo3();
}
function geoInfo1(){
return new Promise((resolve)=>{
fetch('http://localhost:8088/api/getinfo1').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server);
resolve();
}
})
})
}
function geoInfo2(){
return new Promise((resolve)=>{
fetch('http://localhost:8088/api/getinfo2').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server);
resolve();
}
})
})
}
function geoInfo3(){
return new Promise((resolve)=>{
fetch('http://localhost:8088/api/getinfo3').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server);
resolve();
}
})
})
}
</script>
</body>
</html>
可以看到代码的输出结果按照同步主线程方式,按顺序执行
2 发布订阅模式
本文主要介绍如何用发布订阅模式解决回调地狱,在这之前,咱先实现一个最基本的发布订阅模式;
通俗讲:就是先定义函数存下来;然后根据名称把函数读出来执行,函数用一个对象管理。
订阅:将事件名和对应的函数添加到事件对象,属于定义函数;一个名称对应一个函数数组,里面存储多个函数
发布:从事件对象中获取到事件名对应的所有函数,遍历执行;
取消订阅:把之前添加的某个函数从事件对象中删除即可
代码如下:
class PubSub {
constructor() {
// 一个对象存放所有的消息订阅
// 每个消息对应一个数组,数组结构如下
// {
// "event1": [cb1, cb2]
// }
// this.events={
// "event1":[
// fn1,
// fn2
// ],
// "event2":[
// fn3,
// fn4
// ]
// }
this.events = {}
}
// 订阅
subscribe(event, callback) {
if(this.events[event]) {
// 如果有人订阅过了,这个键已经存在,就往里面加就好了
this.events[event].push(callback);
} else {
// 没人订阅过,就建一个数组,回调放进去
this.events[event] = [callback]
}
}
// 发布
publish(event, ...args) {
// 取出所有订阅者的回调执行
const subscribedEvents = this.events[event];
if(subscribedEvents && subscribedEvents.length) {
subscribedEvents.forEach(callback => {
callback.call(this, ...args);
});
}
}
// 取消订阅
unsubscribe(event, callback) {
// 删除某个订阅,保留其他订阅
const subscribedEvents = this.events[event];
if(subscribedEvents && subscribedEvents.length) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
}
调用:
let pb=new PubSub();
// 调用订阅方法,添加一个event1消息,并给消息添加一个行为 (定义函数)
pb.subscribe('event1',function(){
console.log('event1的第一个callback')
})
// 调用订阅方法,添加一个event1消息,并给消息添加一个行为 (定义函数)
pb.subscribe('event1',function(){
console.log('event1的第二个callback')
})
pb.publish('event1') // 发布(调用函数)
3 发布订阅模式解决回调地狱
上面介绍了发布订阅模式的原理;先定义后调用;所以根据这个原理去解决;
重点:
(1)首先全部订阅
(2)在上一个请求的回调函数中,调用下一个请求的发布
所以代码如下
// 异步,info1的发布放到异步队列
fetch('http://localhost:8088/api/getinfo1').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server); // 第一执行
pb.publish('info1')
}
})
// 订阅info1是主线程;所以先执行订阅info1,但是回调函数是异步,info2的异步放到异步队列
pb.subscribe('info1',()=>{
fetch('http://localhost:8088/api/getinfo2').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server); // 第二执行
pb.publish('info2')
}
})
})
// 订阅info2是主线程;所以先执行订阅info2,但是回调函数是异步,info3的异步放到异步队列
pb.subscribe('info2',()=>{
fetch('http://localhost:8088/api/getinfo3').then((response)=>{return response.json()}).then((res)=>{
if(res.code==='0'){
console.log(res.data.server); // 第三执行
pb.publish('info3')
}
})
})
结果显示同步执行,所以实现了同步线程的执行效果;