承诺链与简约

时间:2017-07-14 05:21:53

标签: node.js express promise

我有一个Node 8 / Express 4 / Mongoose 4 API,并希望概括一些代码,以便我可以将其重用于其他部分。

考虑以下用于创建新用户的代码:

function postUser(req, res, next) {
  var body = req.body;
  if ("data" in body) {
    var user = new User(body.data);
    user.save(function(err, savedUser) {
      if (err) {
        if (err.name === 'MongoError' && err.code === 11000) {
          // user already exists
          res.status(400).json({status: "fail", message: "User already exists"});
        } else {
          return next(err);
        }
      } else {
        // user successfully saved
        res.json({status: "success", data: savedUser});
      }
    });
  } else {
    // malformed body
    res.status(400).json({status: "fail", message: "Malformed body"});
  }
}

让我们假设我有其他功能可以做类似的工作,其中一些是回调地狱。我怎样才能最好地概括上面的代码?我想过使用像这样的promise-chains:

function postUser(req, res, next) {
  validateBody(req.body)
  .then(createNewUser)
  .then(user => sendUser(user, res))
  .catch(e => handleErrors(e, res));
}

function validateBody(body) {
  return new Promise(function(resolve, reject) {
    if ("data" in body) {
      resolve(body.data);
    } else {
      reject(new InvalidBodyError());
    }
  });
}

function createNewUser(userObj) {
  return new Promise(function(resolve, reject) {
    var user = new User(userObj);
    user.save(function(err, savedUser) {
      if (err) {
        if (err.name === 'MongoError' && err.code === 11000) {
          // user already exists
          reject(new UserAlreadyExistsError(userObj));
      } else {
          // other error
          reject(err);
        }
      } else {
        // user successfully saved
        resolve(savedUser);
      }
    });
  });
}

function handleErrors(e, res) {
  if (e instanceof InvalidObjectIdError) handleInvalidObjectIdError(e, res)
  else if (e instanceof UserNotFoundError) handleUserNotFoundError(e, res)
  else if (e instanceof InvalidBodyError) handleInvalidBodyError(e, res)
  else if (e instanceof UserAlreadyExistsError) handleUserAlreadyExistsError(e, res)
  // TODO: handle unknown errors
}

正如您所看到的,它看起来更干净,更可重复使用。但它如何在负载下执行?我特别担心每个请求创建多个promise。这是否规模?

解决它的另一种方法是创建一个通用的基类来解决泛型的东西,然后用特定于实现的方法(伪代码)扩展这个类:

class Action {
  constructor() {}

  postDoc(Base, req, res, next) {
    var body = req.body;
    if ("data" in body) {
      var doc= new Base(body.data);
      doc.save(function(err, savedDoc) {
        if (err) {
          if (err.name === 'MongoError' && err.code === 11000) {
            // docalready exists
            res.status(400).json({status: "fail", message: "Doc already exists"});
          } else {
            return next(err);
          }
        } else {
          // user successfully saved
          res.json({status: "success", data: savedDoc});
        }
      });
    } else {
      // malformed body
      res.status(400).json({status: "fail", message: "Malformed body"});
    }
  }
}

class UserAction extends Action {
    constructor() {
      super();
    }

    postUser(body, req, res, next) {
      this.postDoc(User, req, res, next);
    }
}

class AnotherAction extends Action {
    constructor() {
      super();
    }

    postAnother(body, req, res, next) {
      this.postDoc(AnotherBase, req, res, next);
    }
}

然后只使用UserAction或AnotherAction(在我的情况下,User是一个猫鼬模型)。 你更喜欢哪一个?

1 个答案:

答案 0 :(得分:0)

  

我想过使用像这样的promise-chains,它更干净,更可重用。但它如何在负载下执行?

很好。

  

我特别关注为每个请求创建多个承诺。这是否规模?

是。承诺很便宜。看看你为每个请求创建了多少其他对象和回调闭包 - 它完全相同。

但是,您可以进一步简化:

function validateBody(body) {
  return "data" in body
    ? Promise.resolve(body.data)
    : Promise.reject(new InvalidBodyError());
}

function createNewUser(userObj) {
  return new Promise(function(resolve, reject) {
    new User(userObj).save(function(err, savedUser) {
      if (err) reject(err);
      else resolve(savedUser);
    });
  }).catch((err) => {
    if (err.name === 'MongoError' && err.code === 11000) {
      // user already exists
      throw new UserAlreadyExistsError(userObj);
    } else {
      // other error
      throw err;
    });
  });
}
  

解决它的另一种方法是创建一个通用的基类来解决泛型的东西,然后用特定于实现的方法扩展这个类

不,不要这样做。继承是错误的工具。创建像postDoc这样的通用辅助函数,它可以对要创建的文档类型进行抽象,这是一个好主意,但没有充分的理由将它们放在class es中。将它们与承诺结合起来。如果辅助函数的参数数量失控,您可以使用对象,甚至是class,但不要使用继承 - 请改用不同的实例。例如,代码可能如下所示:

const userAction = new Action(User, UserAlreadyExistsError);
const anotherAction = new Action(AnotherBase, …);

function post(action) {
   return (req, res, next) => {
     action.postDoc(req)
     .then(doc => send(doc, res))
     .catch(e => handleErrors(e, res));
   };
}