[发布订阅模式解决回调地狱]:定义发布订阅、解决回调地狱

55 篇文章 4 订阅
35 篇文章 1 订阅

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')
        }
    })
})

结果显示同步执行,所以实现了同步线程的执行效果;

如果对上面代码的执行流程不清楚,可以了解下同步主线程,宏任务和微任务的相关知识;移步异步操作中的宏任务(macrotasks)与微任务(microtasks)_眼眸中的温柔的博客-CSDN博客JS是单线程的运行机制;js的程序代码可以分为同步任务和异步任务,对于同步的任务,当然按照顺序进行执行,但是对于异步的操作,会有一个优先级的执行顺序,分别为宏任务和微任务1 宏任务有哪些:宏任务本质:参与了事件循环的任务;即会被添加到任务队列2 微任务有哪些微任务本质:直接在 Javascript 引擎中的执行的,没有参与事件循环的任务。当成普通的回调理解即可3 根据代码分析任务队列是一种数据结构,可以存放要执行的任务。它符合队列“先进先出”的特点,也就是说要添加任务的...https://blog.csdn.net/popstarqq/article/details/122705806?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164839653216782092923648%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=164839653216782092923648&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-122705806.nonecase&utm_term=%E5%AE%8F%E4%BB%BB%E5%8A%A1&spm=1018.2226.3001.4450

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨大大28

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值