10分钟了解回调地狱
2024-12-09 11:13:16

回调地狱这个概念,单纯是古早时期写接口都是很多人用原生ajax写,然后不断嵌套之后太深,导致代码阅读极为麻烦。

这不是一个官方概念,而是很多开发者口口相传的概念,之后官方推出了Promise,用链式调用,解决了这种深层嵌套形成的问题

不过,回调地狱这个戏称便是一直留了下来,如同前端开发中的一个梗。

一般不会有面试官去面试这个,但是既然都了解到回调相关的事情了,就顺便整理一下。

正文

回调地狱不是一个严肃的概念,仅做了解,不需要深入了解,因为JS进入了ES6的时代后,有了promise,这种代码逻辑的情况很少会存在。

所以,本篇并不做详细了解。

什么是回调地狱

在回调函数中,执行另一个函数,又在另一个函数中继续执行其它函数,如此层层嵌套,就会出现一个嵌套结构,就会形成回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
//有多个异步任务,要求需要同时拿到所有异步任务的结果,下边就是用回调地狱
$.get("url", (res1) => {
conosle.log(res1)
$.get("url+res1", (res2) => {
conosle.log(res2)
$.get("url+res2", (res3) => {
conosle.log(res3)
$.get("url+res3", (res4) => {
conosle.log(res4)
})
})
})
})

上面代码就是一个简单的回调地狱示例,这样的代码肉眼可见的维护性差,可读性也不好

回调地狱的解决方案

这种层层嵌套的处理逻辑,在有些场景中确实是存在的,虽然我们不能消灭它们,但是我们可以优化它们

Promise 解决回调地狱

Promise是一个对象里面保存着某个未来才会结束的事件就是一个异步操作的结果,从它可以获取异步操作的消息。

Promise对象的出现就是进行处理各种异步操作,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数也就可以用于解决回调地狱。

譬如我们项目任务中最常用的接口封装,就是一种最简单的回调地狱解决的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("a");
}, 1000);
})
.then((res) => {
console.log("1秒后打印, res1", res); //1秒后打印 a
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + "a");
}, 1000);
});
})
.then((res) => {
console.log("2秒后打印, res", res); //2秒后打印 aa
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + "a");
}, 1000);
});
})
.then((res) => {
console.log("3秒后打印, res3", res); //3秒后打印 aaa
});

第一个then方法传入的回调函数,返回的是另一个Promise对象。

这时,第二个then方法传入的回调函数,就会等待这个新的Promise对象状态发生变化,只有新的Promise对象状态改变了才会触发后面代码的执行。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数,这样就将异步操作以同步操作的流程表达出来了。

promise解决回调地狱原理 : 在then方法中返回一个promise对象(链式语法嵌套,需要在上一个promise对象的then方法中返回下一个promise)

ES6异步函数async与await

async函数ES2017中引入的更为高级的异步处理机制,可以让异步的处理变的更加便捷,相当于是promise语法的 “高级写法”。

async和await异步函数 : 这两个关键字只能用于函数, 所以用的时候一定要放在函数里面用

  • async关键字: 修饰函数, 表示这个函数内部有异步操作。
  • await关键字: 等待异步执行完毕。

注意点:

  • await 后面是promise对象, 左侧的返回值就是这个promise对象的then方法中的结果
  • await必须要写在async修饰的函数中,不能单独使用,否则程序会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getPromise(params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(params)
}, 1000)
})
}

const getData = async function() {
let data1 = await getPromise("a");
console.log(new Date().getTime(), data1)

let data2 = await getPromise(data1 + "a");
console.log(new Date().getTime(), data2)

let data3 = await getPromise(data2 + "a");
console.log(new Date().getTime(), data3)
}

getData()

Axios网络请求工具

Axios是一个基于Promise对象的网络请求框架

它只是一个网络请求工具并不是网络请求技术,依然是基于XHR开发封装的方式,只是比AJAX更好用。

前端网络请求技术有AJAX技术和JSONP技术以及Fetch技术,这个网络请求框架可以用于浏览器和node.js,AJAX,FETCH,AXIOS三者的关系可以看这篇:有同学问我:Fetch 和 Ajax 有什么区别? - 掘金 (juejin.cn)

这个框架网络请求依然不能解决跨域问题,需要代理服务器才能跨域请求。

多次网络请求需要嵌套时避免层层回调使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let p1=axios('/ajax1')

p1.then((data)=>{
console.log(111111111,data)
return axios('/ajax2')
})
.then((data2)=>{
console.log(data2.data)
return axios('/ajax3')
})
.then((data3)=>{
console.log(data3.data)
return axios('/ajax4')
})
.then((data4)=>{
console.log(data4.data)
})
.catch((e)=>{
console.log(e)
})

Fetch

Fetch之前提过,是区别于传统AJAX的一项新方式,是XMLHttpRequest的升级版也是单独的一个前端网络请求技术。

Fetch原理是使用Promise语法不使用回调函数,用于在JavaScript脚本里面发出HTTP请求,是浏览器自带的API,后端node.js不能使用。

在用法上,fetch()接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。fetch()接收到的数据需要先进行数据转码,来得到想要的数据。

多次网络请求需要嵌套时避免层层回调使用方法

1
2
3
4
5
6
7
fetch("/ajax3")
.then((response)=>{
return response.json()//得到 JSON 对象
})
.then((data)=>{
console.log(data)//真正请求到的数据
})

结语

本篇仅供了解,没有什么太大的作用,回调地狱的那段往事已经是上一代老油条开发的梗了,没必要深入了解的。

上一代的故事就让它停留在上一代吧,可以了解,但是没必要深入。

参考

有同学问我:Fetch 和 Ajax 有什么区别?

【JavaScript】【回调】回调函数 && 回调地狱 - 掘金 (juejin.cn)