NodeJS + Mongoose - 需要帮助使用Promises重构代码

时间:2018-01-01 18:35:39

标签: javascript node.js mongoose promise

考虑这个例子:

router.post('/', function (req, res, next) {

var order = new Order({

    customer_id: req.body.customer_id,
    order_elements: []
});

order.save(function(err, saved_order) {

    var elementsSaveCount = 0;

    req.body.order_elements.forEach( function(order_element, index) {

        orderElement = new OrderElement({

            order_id: saved_order._id,
            name: order_element.name
        });

        orderElement.save(function(err, result) {

            elementsSaveCount++;
            order.order_elements.push(result._id);

            if(elementsSaveCount >= req.body.order_elements.length) {

                saved_order.save();
                res.status(201).json({
                message: 'order saved',
                order: order
                });
            }
        });
    });
});
});

'订单' model有一个id为' OrderElements'的数组。 首先我保存Order,然后在save()的回调中循环遍历请求的OrderElements以保存它们并将每个id推送到Order中。 我使用变量' elementsSaveCount'检查所有save()的执行时间。

我想使用promises重构此代码。

2 个答案:

答案 0 :(得分:0)

这种方法可能是一个开始:

router.post('/', function (req, res, next) {

    var order = new Order({
        customer_id: req.body.customer_id,
        order_elements: []
    });

    order.save()
    .then(saved_order => {
        var elementsSaveCount = 0;
        var listPromises = [];
        for(let order_element of req.body.order_elements) {
            let orderElement = new OrderElement({
                order_id: saved_order._id,
                name: order_element.name
            });

            listPromises.push(orderElement.save());
        }

        return Promise.all(listPromises);
    })
    .then(allOrderElementsSaved => {
        allOrderElementsSaved.map(orderElementSaved => {
            saved_order.order_elements.push(orderElementSaved._id);  
        });

        return saved_order.save();
    })
    .then(saved_order => {
        res.status(201).json({
            message: 'order saved',
            order: saved_order.toObject()
        });
    });
});

答案 1 :(得分:0)

如果我实际上正在重构给定的代码示例,那么这不是我要做的事情,但是我将尝试说明Promises将帮助我们重构处理程序的几种不同方式:

router.post('/', (req, res, next) => {
  const saveOrdersWithParent = parentOrderId => req.body.order_elements
  .map(order_element =>
    new OrderElement({
      order_id: parentOrderId, name: order_element.name
    }).save()
  );

  return new Order({
    customer_id: req.body.customer_id,
    order_elements: []
  }).save()
  .then(saved_order =>
    Promise.all(saveOrdersWithParent(saved_order._id))
    // if you want to catch invidiually failed items separately,
    // otherwise omit this and the next catch will get it
    .catch(err => {
      console.error('Some individual order element could not be saved', err);

      // Important: we need to "bubble up" the error if we want to handle it
      // specially or else the next 'then' will go ahead and run but of
      // course it's `orders` param will be undefined.
      // Usually avoid this situation but it's good to know
      throw err;
    }).then(orders => {
      // assuming you wanted this to be persisted to the database
      // and not just the local representation
      saved_order.order_elements = orders.map(o => o._id);
      return saved_order.save();
    })
  ).then(order => res.status(201).json({ message: 'order saved', order }))
  .catch(err => console.error('Original order could not be saved', err));
});

首先,Promises的优点在于它们非常容易构图,因为您可以轻松地将它们视为值。因此,您会注意到我将一些有点混乱的逻辑(创建并保存订单元素)分解为一个返回Promise数组的辅助函数。不是非常必要,但它是帮助清理承诺链的好工具。使用Promises使复杂的异步代码更清晰时,真正关键的是使链本身尽可能地裸露,努力做到这样的事情:createPost().then(persistPost).then(notifyUsersOfPost).then(etc)它真正清楚在高层发生了什么并隐藏了细节。你也可以通过回调来做到这一点,但这要困难得多。

Promise.all可用于等待所有订单元素成功保存,从而无需计数器。如果未成功保存任何订单元素,它将直接跳到catch块,您可能会处理某些订单元素未保存的情况。只有你能以某种方式恢复,或者通过重试(参见代码示例中的注释),才能真正做到这一点。

所以我们在这里有一些嵌套,比如回调,但更少。通常,您希望在嵌套的promises中操作,以显示对象的“生命周期”。在此示例中,嵌套的promises(保存订单元素)需要引用最初保存的订单,因此我们可以非常清楚地看到代码的哪些部分依赖于该(中间)值。在我们不需要saved_order价值的那一刻,我们就会重新回到顶级承诺链。同样有效的事情可能是这样的:

.then(saved_order => {
  const orderSaves = saveOrdersWithParent(saved_order._id)
    .catch(err => {
      console.error('Some individual ...', err);
      throw err;
    })
  return Promise.all([ saved_order, orderSaves... ]);
}).then(([ saved_order, ...orders ]) => {
  saved_oder.order_elements = orders.map(o => o._id);
  return saved_order.save()
})
...

这可以通过包含我们需要的值(持久化Order对象)以及保存的订单元素来避免嵌套,但IMO通常不太清楚。

我认为这个特殊的例子很好地展示了如何创建操作的“管道”,这些操作被事物需要的输入和产生的内容所阻挡。胜利的是逻辑的步骤清晰,取决于什么。