浅分析前端回调地狱

647 阅读6分钟

据说某俄国特工经过九死一生偷到了NASA的太空火箭发射程序的源代码的最后一页,代码是 ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

为了清楚的让大家了解这个冷笑话,下面我将用一个小案例为大家讲述。

红黄绿灯demo

假设我们要制作一个红黄绿灯的小demo,我们首先会创建ul标签,在ul标签中插入三个子节点,并把这个子节点设置成三个圆,接着给父节点设置监听器,每隔一秒就给子节点换不同的颜色。

<body>
    //创建一个ul标签并插入三个子节点
    <ul>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</body> 

border-radious:50%,意思是在我这个盒子里面每个顶角上都会形成一个圆,圆的宽和高分别就是我这个盒子宽和高的一半,之后再去掉四个顶角,这样我就给这个盒子加圆角了。一旦我们把外面的盒子设置成了正方形,四个内圆重叠在了一起,我们里面取得的圆就是个标准圆。

基本布局完之后,页面如下,三个完全相等的圆。

添加监听器

为了使这个demo还具有动画效果,我们还需要使用监听器。在父节点里每隔一秒我就给这个父节点添加一个类选择器名称,由于我之前写了三个类选择器,分别为stop,warn,pass,因此每隔一秒我只需要改变类选择器的名称就可以了。

 const ul = document.querySelector('ul');
        setTimeout(() => {
            ul.className = 'stop';
            setTimeout(() => {
                ul.className = 'warn';
                setTimeout(() => {
                    ul.className = 'pass';
                }, 1000);
            }, 1000);
        }, 1000); 

我们再来运行一下,这时候页面就顺利的动起来了,开心!

小bug

不知道你没有发现,这样运行的话我们只能循环一次,并没有达到我们理想的一直循环下去的状态,怎么办呢?简单啊,我再加几个监听不就可以了吗,好的,接下里我们就多加几个监听看看有什么变化。

const ul = document.querySelector('ul');
        setTimeout(() => {
            ul.className = 'stop';
            setTimeout(() => {
                ul.className = 'warn';
                setTimeout(() => {
                    ul.className = 'pass';
                    setTimeout(() => {
                        ul.className = 'stop';
                        setTimeout(() => {
                            ul.className = 'warn';
                            setTimeout(() => {
                                ul.className = 'pass';
                                setTimeout(() => {
                                    ul.className = 'stop';
                                    setTimeout(() => {
                                        ul.className = 'warn';
                                        setTimeout(() => {
                                            ul.className = 'pass';
                                        }, 1000);
                                    }, 1000);
                                }, 1000);
                            }, 1000);
                        }, 1000);
                    }, 1000);
                }, 1000);
            }, 1000);
        }, 1000);

再运行一下,貌似是成功了,能循环好几次,难道这就是我们理想的demo吗?不是的,因为上述的代码,你会发现有无数个回调函数,这就是传说中的 某俄国特工九死一生偷到了NASA太空火箭发射程序,结果是JS的回调地狱,你从左边侧着看是不是特别像一个火箭。

好了,到目前为止我们介绍的都是为了今天的重点Promise做的铺垫,接下来我们将正式进入重点Promise。

认识Promise

所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件。在前端编程中,我们经常会使用Promise对象来处理异步事件。

Promise对象的状态不受外界影响,它有三个状态(Pending, Fullfilled, Rejected),一旦它的状态改变就不会再变了。下面是Promise的响应流程。 简单点,Promise的响应过程可以划分为两个步骤,第一在进程中,第二成功了/失败了。

如果我们不执行异步操作,我们的页面就必须得到服务器的响应后才能做其他操作,这无疑浪费了很多资源。Promise不仅是异步编程的一种解决方案,它还能将异步操作以同步操作的流程表达出来。

下面的代码为一个Promise的实例

let p = function () {
            return new Promise(function (resolve, reject) {
                setTimeout(() => {
                    resolve();
                }, 1000);
            })
        }

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject,它们是两个函数,由JavaScript引擎提供,不用自己部署。

在上面的匿名函数中我许下了一个Promise,时间是1s以后要干的事。1s以后Promise的状态为resolve(),说明Promise对象的状态由 进行中 转变到 已成功 状态,接着就可以进行回调函数。

new Promise((resolve, reject) => {
    resolve(1);
    console.log(2);
})
.then(value => {
    console.log(value);
})
// 2
// 1 

上述代码便是一个完整的Promise对象的链式操作了,Promise then 是串行结构,而回调则是嵌套结构,每一个then结束后都将返回Promise,状态为Resolved,因此,我可以串联多个then,异步执行多个操作。上面的代码中,调用resolve(1)以后,后面的console.log(2)还会执行,并且会首先打印出来,这是因为立即Resolved的Promise是在本轮事件循环的末尾执行,总是晚于本次循环的同步任务。

使用Promise链式调用后的demo

连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。见证奇迹的时刻:

在light函数中,我用了三次then,绑定了三个回调函数。 在 第一个then 中我给ul这个标签加了一个红灯的css规则,之后这个回调函数返回我的promise对象,因为我的Promise对象状态还是resolve(成功的),我还可以接着then,所以1s之后我又在 第二个then 中把ul标签改为黄灯的css规则,最后1s之后 第三个then,我再把ul标签改成绿灯的css规则。经过三个then后我就可以实现一次红绿灯的跳动了,间隔都是1s跳一次。

注意:then回调函数一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。

最后,如果想让它实现连续循环的效果,我们还可以添加一个监听器。

setInterval(() => {
            light();
        }, 3000); 

这样我就实现了每隔一秒跳动一次,每隔三秒循环一次的效果了。相比于之前的嵌套结构,这个链式调用简便了很多。虽然都是同样的效果,但是Promise的链式调用会让你的代码简化很多,因此,为了不让自己掉入JS的回调地狱,Promise对象用起来吧!

总结

关于Promise的链式调用就介绍到这里,欢迎大家给予建议和交流,如果有问题可以在下方评论,看到了会及时回复哒!