避免在 Express.js 应用程序中使用回调地狱

在编写 Express.js 应用程序时,我们经常需要编写异步代码,处理数据库查询、I/O 操作和网络请求等。然而,简单的异步嵌套容易导致回调地狱,增加代码的复杂度和维护成本。在本文中,我们将介绍避免回调地狱的方法,从而编写更清晰和可维护的代码。

回调地狱的问题

回调地狱是指在嵌套多个回调函数中,因为多层嵌套或逻辑错误,导致代码不可读、难以维护和容易出错的情况。

例如,下面是一个简单的 Express.js 应用程序,用于获取用户列表和用户详情。在这个例子中,我们使用了嵌套的异步回调函数,导致代码不可读和难以维护。

const express = require('express');
const app = express();
const port = 3000;

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

app.get('/users', (req, res, next) => {
  getUserList(users, (err, userList) => {
    if (err) {
      return next(err);
    }
    res.send(userList);
  });
});

function getUserList(users, callback) {
  const userIds = users.map(user => user.id);
  getUserDetails(userIds, (err, userDetails) => {
    if (err) {
      return callback(err);
    }
    const userList = users.map(user => ({
      id: user.id,
      name: user.name,
      details: userDetails[user.id],
    }));
    callback(null, userList);
  });
}

function getUserDetails(userIds, callback) {
  const userDetails = {};
  userIds.forEach(id => {
    fetchUserDetails(id, (err, details) => {
      if (err) {
        return callback(err);
      }
      userDetails[id] = details;
      if (Object.keys(userDetails).length === userIds.length) {
        callback(null, userDetails);
      }
    });
  });
}

function fetchUserDetails(userId, callback) {
  setTimeout(() => {
    if (userId === 1) {
      callback(null, { age: 20, gender: 'female' });
    } else if (userId === 2) {
      callback(null, { age: 30, gender: 'male'});
    } else {
      callback(new Error('User not found'));
    }
  }, 1000);
}

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

在此示例中,我们使用了嵌套的回调函数 getUserDetailsfetchUserDetails。虽然这个示例程序比较简单,但是如果有更复杂的异步操作,就会导致代码的可读性和维护性非常差。

Promise

为了避免回调地狱,我们可以使用 Promise 对象,从而编写更清晰和可维护的代码。Promise 是一种可以表示异步操作的对象,它代表了一个异步操作的最终完成或失败,并返回一个结果。

Promise 对象有三种状态:pending、fulfilled 和 rejected。一旦 Promise 进入 fulfilled 或 rejected 状态,就无法再改变其状态。

在 Express.js 应用程序中,我们可以使用 Promise 对象来处理异步操作,从而避免回调地狱。

例如,下面的示例使用 Promise 对象来获取用户列表和用户详情。在这个示例中,我们使用了 Promise.all() 方法来并行处理请求,这样可以保证异步请求的效率和性能。

const express = require('express');
const app = express();
const port = 3000;

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

app.get('/users', async (req, res, next) => {
  try {
    const userList = await getUserList(users);
    res.send(userList);
  } catch (err) {
    next(err);
  }
});

function getUserList(users) {
  const userIds = users.map(user => user.id);
  const promises = [
    getUserDetails(userIds),
  ];
  return Promise.all(promises)
    .then(([userDetails]) => {
      const userList = users.map(user => ({
        id: user.id,
        name: user.name,
        details: userDetails[user.id],
      }));
      return userList;
    });
}

function getUserDetails(userIds) {
  const userDetails = {};
  const promises = userIds.map(id => {
    return fetchUserDetails(id)
      .then(details => {
        userDetails[id] = details;
      });
  });
  return Promise.all(promises)
    .then(() => userDetails);
}

function fetchUserDetails(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (userId === 1) {
        resolve({ age: 20, gender: 'female' });
      } else if (userId === 2) {
        resolve({ age: 30, gender: 'male'});
      } else {
        reject(new Error('User not found'));
      }
    }, 1000);
  });
}

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

在此示例中,我们使用了异步函数和 Promise 对象,从而避免了回调地狱。使用 async / await 可以更清晰和易读地处理异步操作。使用 Promise.all() 可以并行处理多个异步请求,从而提高了效率和性能。

总结

回调地狱是一个常见的编码问题,可以通过使用异步函数和 Promise 对象来避免。在编写 Express.js 应用程序时,使用 Promise 可以使代码更清晰、可读性更高,并提高效率和性能。

在实际应用中,避免回调地狱需要多练习和实践。希望本文能给您带来一些指导和参考,帮助您编写更清晰和可维护的代码。

参考资料:

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65917b38eb4cecbf2d69bb60


纠错
反馈