考虑这个例子:
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重构此代码。
答案 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通常不太清楚。
我认为这个特殊的例子很好地展示了如何创建操作的“管道”,这些操作被事物需要的输入和产生的内容所阻挡。胜利的是逻辑的步骤清晰,取决于什么。