考虑这个例子:
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
});
}
});
});
});
});
“Order”模型有一个 id 为“OrderElements”的数组。 首先,我保存订单,然后在 save() 的回调中循环访问请求的 OrderElements 以保存它们,并将每个 id 推送到订单内。 我使用变量“elementsSaveCount”来检查所有 save() 何时执行。
我想使用 Promise 重构此代码。
最佳答案
严格来说,如果我实际上重构给定的代码示例,这并不是我会做的事情,但我将尝试说明 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));
});
首先,Promise 的好处是它们非常容易编写,因为您可以轻松地将它们视为值。因此,您会注意到我将一些有点困惑的逻辑(创建和保存订单元素)分解为一个返回 Promise 数组的辅助函数。不是非常必要,但它是帮助清理 promise 链的好工具。使用 Promises 使复杂的异步代码更清晰时,真正关键的是使链本身尽可能裸露,争取像这样的东西:createPost().then(persistPost).then(notifyUsersOfPost).then(etc)
高层发生的事情非常清楚,但隐藏了细节。您也可以通过回调来做到这一点,但这要困难得多。
Promise.all
可用于等待所有订单元素成功保存,从而无需计数器。如果任何订单元素未成功保存,它将直接跳至 catch
block ,您可以在其中处理某些订单元素未保存的情况。仅当您可以通过某种方式恢复时才真正执行此操作,也许可以通过重试(请参阅代码示例中的注释)。
所以我们这里确实有一些嵌套,比如回调,但要少得多。通常,您希望在嵌套的 Promise 中进行操作,作为显示对象“生命周期”的一种方式。在此示例中,嵌套的 Promise(保存订单元素)需要原始保存订单的引用,因此我们可以非常清楚地看到代码的哪些部分依赖于该(中间)值。当我们不需要那个的时候 saved_order
我们将值(value)回溯到顶层 promise 链。同样有效的事情可能是这样的:
.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 通常不太清楚。
我认为这个特定的示例很好地展示了如何创建操作的“管道”,这些操作被事物需要的输入和生成的内容所阻止。胜利在于清晰的逻辑步骤以及什么取决于什么。
关于javascript - NodeJS+Mongoose - 需要帮助使用 Promises 重构代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48052238/