JS中如何解决回调地狱

在JavaScript中,"回调地狱"(Callback Hell)是指在异步编程中,多个回调函数嵌套在彼此之内,形成深层次的嵌套结构,使得代码难以阅读和维护的情况。这种情况通常出现在处理多个异步操作的场景中,例如在Node.js中处理文件读取、数据库操作或者进行网络请求等。

回调地狱的问题主要包括:

  • 代码可读性差: 大量嵌套的回调函数使得代码难以理解和维护,因为你需要在多个层次中找到相应的逻辑。

  • 错误处理困难: 错误处理变得更加复杂,因为你需要在每个回调函数中检查和处理错误。

  • 难以调试: 调试复杂的嵌套结构变得困难,定位问题和理清代码执行流程变得挑战性。

比如:

queryList(function(result1) {
    filterNegative(result1, function(result2) {
        calcSum(result2, function(result3) {
            // ... 更多嵌套操作
        });
    });
});

function queryList(callback) {
    setTimeout(function() {
        console.log("从数据库中查询数据");
				let list = [-1, -2, -3, 1, 2, 3, 4, 5, 6]
        callback(list);
    }, 1000);
}

function filterNegative(list, callback) {
    setTimeout(function() {
        console.log("过滤负数 input: " + list);
				list = list.filter(i => i >= 0);
        callback(list);
    }, 1000);
}

function calcSum(list, callback) {
    setTimeout(function() {
        console.log("求和: " + list);
				let sum = list.reduce((acc, currentValue) => acc + currentValue, 0);
        callback(sum);
    }, 1000);
}

为了解决回调地狱的问题,JavaScript社区采用了一些异步编程的解决方案,例如使用Promise、async/await等。这些工具可以使异步代码更具可读性,减少回调函数的嵌套,提高代码的可维护性。

Promise

Promise 是 JavaScript 中用于处理异步操作的对象。它代表一个异步操作的最终完成或失败,以及它的返回值。

一个 Promise 可以处于以下三个状态之一:

  • Pending(进行中): 初始状态,表示操作还在进行中。
  • Fulfilled(已成功):表示操作已成功完成,并返回了一个值(通常是 resolve 函数的参数)。
  • Rejected(已失败): 表示操作由于某些原因失败,并返回了一个原因(通常是 reject 函数的参数)。

Promise 构造函数接受一个带有两个参数的执行函数:resolve 和 reject。resolve 用于将 Promise 状态从 pending 变为 fulfilled,而 reject 用于将 Promise 状态从 pending 变为 rejected。

以下是一个简单的 Promise 示例:


const myPromise = new Promise((resolve, reject) => {
    // 异步操作,比如从服务器获取数据

    const success = true; // 模拟异步操作成功

    if (success) {
        resolve("Operation successful!"); // 将 Promise 状态设置为 Fulfilled
    } else {
        reject("Operation failed!"); // 将 Promise 状态设置为 Rejected
    }
});

// 处理 Promise 结果
myPromise
    .then((result) => {
        console.log(result); // 在 Promise 成功时执行
    })
    .catch((error) => {
        console.error(error); // 在 Promise 失败时执行
    });

但是Promise最大的问题就是代码冗余,原来的异步任务被Promise封装一下,不管什么操作都用than,就会导致一眼看过去全是then…then…then…,这样也是不利于代码维护的。

所以下面的async/await 可以时代码看起来更像同步代码。

async/await

首先我们看async关键字,他作为一个关键字放到声明函数前面,表示该函数为一个异步任务,不会阻塞后面函数的执行:

async function fn(){
	return '基尼泰梅';
}
console.log(fn());

可以看到async函数返回数据时自动封装为一个Promise对象。

async关键字说完了,我们看看awai关键字, await关键字只能在使用async定义的函数中使用

  • await后面可以直接跟一个 Promise实例对象(可以跟任何表达式,更多的是跟一个返回Promise对象的表达式)
  • await函数不能单独使用, 只能在使用async定义的函数中使用
  • await可以直接拿到Promise中resolve中的数据。

和Promise对象一样,处理异步任务时也可以按照成功和失败来返回不同的数据,处理成功时用then方法来接收,失败时用catch方法来接收数据。

以下是 async/await 的示例代码:

//封装一个返回promise的异步任务
function fn(str) {
	var p = new Promise(function (resolve, reject) {
		var flag = true;
		setTimeout(function () {
			if (flag) {
				resolve(str)
			} else {
				reject('处理失败')
			}
		})
	})
	return p;
}

//封装一个执行上述异步任务的async函数
async function test(){
	var res1=await fn('敲代码敲了两年半');  //await直接拿到fn()返回的promise的数据,并且赋值给res
	var res2=await fn('这也不会');
	var res3=await fn('那也不会');
	console.log(res1, res2, res3);
}

//执行函数
test();