关于js中如何避免回调地狱的问题
很多js新人———嗯,也包括我,在js里第一次写东西的时候,接触到了axios,和很多萌新一样,我最早学会的js请求就只会最基本的回调函数:
axios.get('/api/data')
  .then(response => {
    // 处理数据
    const data = response.data;
    return anotherRequest(data.id); //调用数据
  })
  .then(secondResponse => {
    // 嵌套层级增加......
  })
  .catch(error => {
    // 错误处理
  });
这样搞多了,复杂点的逻辑就会变成灾难性的回调地狱,加一个请求就得从头开始捋...
axios.get(`${API_URL}/users/1`)
  .then(response => {
    console.log('1. 获取用户信息:', response.data.name);
    
    // 获取该用户的帖子
    axios.get(`${API_URL}/posts?userId=${response.data.id}`)
      .then(postsResponse => {
        console.log('2. 获取用户帖子:', postsResponse.data.length);
        
        // 获取第一个帖子的评论
        axios.get(`${API_URL}/posts/${postsResponse.data[0].id}/comments`)
          .then(commentsResponse => {
            console.log('3. 获取帖子评论:', commentsResponse.data.length);
            
            // 获取第一条评论的作者信息
            axios.get(`${API_URL}/users/${commentsResponse.data[0].id}`)
              .then(userResponse => {
                console.log('4. 获取评论作者:', userResponse.data.name);
                
                // 获取该作者的相册
                axios.get(`${API_URL}/albums?userId=${userResponse.data.id}`)
                  .then(albumsResponse => {
                    console.log('5. 获取作者相册:', albumsResponse.data.length);
                    
                    // 结果
                    console.log('6. 最终结果:', {
                      originalUser: response.data.name,
                      postCount: postsResponse.data.length,
                      commentCount: commentsResponse.data.length,
                      commentAuthor: userResponse.data.name,
                      albumCount: albumsResponse.data.length
                    });
                  })
                  .catch(err => console.error('获取相册失败:', err));
              })
              .catch(err => console.error('获取评论作者失败:', err));
          })
          .catch(err => console.error('获取评论失败:', err));
      })
      .catch(err => console.error('获取帖子失败:', err));
  })
  .catch(err => console.error('获取用户失败:', err));
真是看的人想撞墙,不是吗?如果你不系统地学习js,而是像我一样作为一个业余爱好者,学来处理一些基础的业务,很容易写出这样的东西来。
实际上,现在不应当这么麻烦的。现在我们可以使用ES2017引入的async/await方式,来使异步代码看起来更像同步代码,从而提高可读性。通过使用async函数,我们可以在函数内部使用await关键字来等待Promise的解决,这样代码结构更清晰,更易于维护。
async function fetchUserData() {
  try {
    // 1. 获取用户信息
    const userResponse = await axios.get(`${API_URL}/users/1`);
    console.log('1. 获取用户信息:', userResponse.data.name);
    
    // 2. 获取该用户的帖子
    const postsResponse = await axios.get(`${API_URL}/posts?userId=${userResponse.data.id}`);
    console.log('2. 获取用户帖子:', postsResponse.data.length);
    
    // 3. 获取第一个帖子的评论
    const commentsResponse = await axios.get(`${API_URL}/posts/${postsResponse.data[0].id}/comments`);
    console.log('3. 获取帖子评论:', commentsResponse.data.length);
    
    // 4. 获取第一条评论的作者信息
    const commentAuthorResponse = await axios.get(`${API_URL}/users/${commentsResponse.data[0].id}`);
    console.log('4. 获取评论作者:', commentAuthorResponse.data.name);
    
    // 5. 获取该作者的相册
    const albumsResponse = await axios.get(`${API_URL}/albums?userId=${commentAuthorResponse.data.id}`);
    console.log('5. 获取作者相册:', albumsResponse.data.length);
    
    // 6. 最终结果
    const result = {
      originalUser: userResponse.data.name,
      postCount: postsResponse.data.length,
      commentCount: commentsResponse.data.length,
      commentAuthor: commentAuthorResponse.data.name,
      albumCount: albumsResponse.data.length
    };
    
    console.log('6. 最终结果:', result);
    return result;
    
  } catch (error) {
    console.error('请求过程中发生错误:', error.message);
    throw error; 
  }
}
这样,回调地狱消失了,程序变得清晰整洁。
这时候就会有人问了,分开处理了所有的业务,这样性能会受到影响吗,一定程度确实会。我们的程序还有优化的空间:我们可以使用Promise.all,把两个变量放在一起并行请求,节省时间:
async function fetchOptimizedData() {
  try {
    // 1. 获取用户基本信息 (并行获取用户和用户帖子)
    const [userResponse, postsResponse] = await Promise.all([
      axios.get(`${API_URL}/users/1`),
      axios.get(`${API_URL}/posts?userId=1`)
    ]);
    
    console.log('1. 获取用户信息:', userResponse.data.name);
    console.log('2. 获取用户帖子:', postsResponse.data.length);
    // 2. 获取第一个帖子的评论和第一条评论的作者信息 (并行)
    const [commentsResponse, commentAuthorResponse] = await Promise.all([
      axios.get(`${API_URL}/posts/${postsResponse.data[0].id}/comments`),
      axios.get(`${API_URL}/users/${userResponse.data.id}`) // 假设这里需要获取的是评论作者
    ]);
    
    console.log('3. 获取帖子评论:', commentsResponse.data.length);
    console.log('4. 获取评论作者:', commentAuthorResponse.data.name);
    // 3. 获取该作者的相册和待办事项 (并行)
    const [albumsResponse, todosResponse] = await Promise.all([
      axios.get(`${API_URL}/albums?userId=${commentAuthorResponse.data.id}`),
      axios.get(`${API_URL}/todos?userId=${commentAuthorResponse.data.id}`)
    ]);
    
    console.log('5. 获取作者相册:', albumsResponse.data.length);
    console.log('6. 获取作者待办事项:', todosResponse.data.length);
    // 返回最终结果
    return {
      user: userResponse.data,
      postCount: postsResponse.data.length,
      commentCount: commentsResponse.data.length,
      commentAuthor: commentAuthorResponse.data,
      albumCount: albumsResponse.data.length,
      todoCount: todosResponse.data.length
    };
    
  } catch (error) {
    console.error('请求过程中发生错误:', error.message);
    throw error;
  }
}
这样,原本的时间为"请求1+请求2"的时间,现在并行请求就变成了每一次请求中,最慢的那个函数的时间。
有的人觉得这样等待,不会使得被堵塞吗?并不会。这个函数是在程 序中统一async调用。await仅暂停当前 async 函数,并不会阻塞主线程,因为底层基于 Promise,本质仍是异步,不会冻结UI等等;
当然还有最后一个问题:每一次请求都只有获取了,从而报错统一处理。忘记了某一次就会导致未处理 rejection。这个时候建议使用全局的错误监听,这样遇到了也不会导致未处理的错误导致崩溃。
// 浏览器全局错误监听
window.addEventListener('unhandledrejection', (event) => {
  // 阻止默认行为(控制台报错)
  event.preventDefault(); 
  
  console.error('[浏览器] 未处理的 Promise 拒绝:', event.reason);
  
  // 可在此处添加其他逻辑(比如说上报?
  reportErrorToServer(event.reason);
});
// 以及Node.js
process.on('unhandledRejection', (reason, promise) => {
  console.error('[Node.js] 未处理的 Promise 拒绝:', reason);
  
  // 可在此处添加其他逻辑(比如说上报?
  reportErrorToServer(reason);
  
  // 防止进程崩溃(看情况)
  // process.exit(1); // 严重错误时就直接及时止损(爆!)
});
优雅的代码使人事半功倍,这就是我从中得到的最大的收获。









