序言
在实际开发中我们经常会遇到一个程序的运行需要接收上一个程序运行结果的参数才能完成,而且上一个程序的运行需要一定的时间,如果我们直接正常让函数依次执行,会导致结果的输出错乱。举个通俗易懂的例子,我们在用微信发信息的过程其实就是HTTP请求-响应
模式的利用,而包
在传输的过程可能会由于网络的原因而丢包
,如果网络不好,可能你发了一连串的信息,一直在等待响应,然后网络响应后,信息都发出去了但是顺序却错乱了。要解决这个很常见的问题,就得好好聊一聊我们今天要讲的主题异步
和回调地狱
。
异步
js
function a (){ //称a函数正在异步执行代码
setTimeout(function(){
console.log('我得先把菜做完才能刷抖音')
},1000)
}
a()
function b (){
console.log("可以刷抖音啦!")
}
b()
在运行这段代码后,我们会看到"可以刷抖音啦!"
这句话会先打印出来,然后是一秒后才会打印"我得先把菜做完才能刷抖音"
。这是因为JavaScript中的setTimeout
方法会安排一个回调函数
在指定的时间间隔之后执行,但它不会阻塞
后续的代码执行,这时候我们就称函数a正在异步
执行代码。
但是这并不是我们想要的结果,我们希望执行顺序是先把菜做完才能刷抖音,那么我们思考思考有什么办法吗?
有朋友可能会觉得把函数b
放在函数a
里面,并且等函数a
里面的setTimeout方法
执行完毕在执行函数b
,那我们可以将代码改成下面这种,来看看实际效果!
js
function a(cb){
setTimeout(() => {
console.log("我得先把菜做完才能刷抖音");
cb()
},1000)
}
function b(){
setTimeout(() => {
console.log("可以刷抖音啦!");
}, 0);
}
a(b) //输出 "我得先把菜做完才能刷抖音" "可以刷抖音啦!"
在这段代码中,函数a
接受一个回调函数cb
作为参数,并在1秒后执行该回调函数。函数a
的回调函数中打印了"我得先把菜做完才能刷抖音"
,然后调用了cb()
。
函数b
是另一个异步函数
,它在0毫秒后执行,打印了"可以刷抖音啦!"
。
最后,在调用a函数
时,将函数b
作为回调函数
传递给了a函数
。因此,执行顺序如下:
- 调用a函数,并将函数b作为回调函数传入。
- a函数内部设置了一个1秒的定时器,并打印了"我得先把菜做完才能刷抖音"。
- a函数的定时器到期后,执行回调函数cb(),即执行函数b。
- b函数设置了一个0毫秒的定时器,并打印了"可以刷抖音啦!"。
因此,最终的输出结果是:
md
我得先把菜做完才能刷抖音
可以刷抖音啦!
简单来说,函数a
中的setTimeout方法
仍然是在异步
执行代码,并且是在1s后先输出"我得先把菜做完才能刷抖音"
,然后执行函数cb
然后打印"可以刷抖音啦!"
。所以最后结果达到我们所期望的亚子。
我们来思考一个问题:问:cb函数没有执行完毕之前,a算不算执行完毕?
答案是: a算执行完毕 。因为我们是在函数a
里面对cb函数
进行回调
,当函数a
执行完毕后,它的执行上写文
就会被移出调用栈,接着执行函数b
,当函数b
执行完毕,它的执行上下文
也会被垃圾回收机制
移出调用栈。
回调地狱
"回调地狱"
是指在JavaScript中,通过多层嵌套的回调函数
来处理异步
操作所导致的代码结构复杂、难以维护的情况。这种情况通常发生在多个
异步操作需要按照特定顺序执行,并且每个操作的结果都依赖于前一个操作
的结果
时。
例如,下面是一个简单的回调地狱示例:
javascript
asyncOperation1(function(result1) {
asyncOperation2(result1, function(result2) {
asyncOperation3(result2, function(result3) {
// 更多嵌套...
});
});
});
当我们回调函数
嵌套的层数太多时,如果出现了BUG
,我们不能确定是其中哪一层出了问题,因为只要有一个嵌套
出现错误,它后面所有的回调函数
执行结果都会受影响,于是我们需要对剩余部分
的每层嵌套
排查,用这种回调代码来处理异步
的操作是15年前的样式,大大增加了代码的可维护性
难度,难怪以前的程序员头发少T_T,单排查这一个多层嵌套的回调函数就不得不熬夜加班了,所以随着时代的进步,我们也对之做出了相应的策略。
为了避免回调地狱
,可以使用Promise
对象或async/await
关键字来改善代码结构。使用Promise
可以将回调函数改为链式调用
,提高代码的可读性
和可维护性
。使用async/await
可以更直观地编写异步代码,使其看起来更像同步代码,而不会产生过多的嵌套。
总结
今天我们对于异步
执行这个概念和老一代方法---回调函数
来解决异步
有了基本的理解,但是回调地狱
正如其名,恐怖如斯
,磨人心智
,不用担心,在下一篇文章我会为大家详细讲解如何使用ES6
新增特性---Promise
对象以及它的常用方法来处理异步,并且完美的解决了代码的可维护性差
和代码复杂
等问题。
结语
那么到了这里我们今天的文章就结束啦~
创作不易,如果感觉这个文章对你有帮助的话,点个赞吧♥
作者所有文章的源码,给作者的开源git仓库点个收藏吧: gitee.com/cheng-bingw...