[英]Thinking in JavaScript promises (Bluebird in this case)
我试图了解一些不那么琐碎的承诺/异步用例。 在一个我正在努力解决的例子中,我有一个从knex查询(可编组数组)返回的书籍数组我希望插入到数据库中:
books.map(function(book) {
// Insert into DB
});
每个书籍项目如下:
var book = {
title: 'Book title',
author: 'Author name'
};
但是,在我插入每本书之前,我需要从一个单独的表中检索作者的ID,因为这些数据是标准化的。 作者可能存在也可能不存在,因此我需要:
但是,上述操作也都是异步的。
我可以在原始映射中使用promise(获取和/或插入ID)作为插入操作的先决条件。 但问题在于,因为所有内容都是异步运行的,所以代码可能会插入重复的作者,因为初始check-if-author-exists与insert-a-new-author块分离。
我可以想到实现上述目标的几种方法,但它们都涉及拆分承诺链,而且通常看起来有点混乱。 这似乎是一种必须出现的问题。 我确定我在这里缺少一些基本的东西!
有小费吗?
我们假设您可以并行处理每本书。 然后一切都很简单(仅使用ES6 API):
Promise
.all(books.map(book => {
return getAuthor(book.author)
.catch(createAuthor.bind(null, book.author));
.then(author => Object.assign(book, { author: author.id }))
.then(saveBook);
}))
.then(() => console.log('All done'))
问题是获取作者和创建新作者之间存在竞争条件。 请考虑以下事件顺序:
现在我们在作者表中有两个A实例。 这是不好的! 要解决这个问题,我们可以使用传统方法:锁定。 我们需要保留每个作者锁的表。 当我们发送创建请求时,我们锁定适当的锁。 请求完成后,我们解锁它。 涉及同一作者的所有其他操作需要在执行任何操作之前先获取锁。
这似乎很难,但在我们的案例中可以简化很多,因为我们可以使用我们的请求promises而不是lock:
const authorPromises = {};
function getAuthor(authorName) {
if (authorPromises[authorName]) {
return authorPromises[authorName];
}
const promise = getAuthorFromDatabase(authorName)
.catch(createAuthor.bind(null, authorName))
.then(author => {
delete authorPromises[authorName];
return author;
});
authorPromises[author] = promise;
return promise;
}
Promise
.all(books.map(book => {
return getAuthor(book.author)
.then(author => Object.assign(book, { author: author.id }))
.then(saveBook);
}))
.then(() => console.log('All done'))
而已! 现在,如果对作者的请求是飞行,则将返回相同的承诺。
以下是我将如何实现它。 我认为一些重要的要求是:
n
查询到数据库n
东西-避免了经典的“N + 1”的问题。 我使用一个事务,以确保更新是原子的 - 即如果操作运行并且客户端在中间死亡 - 没有作者是在没有书籍的情况下创建的。 同样重要的是,临时故障不会导致内存泄漏(例如在作者映射的答案中保持失败的承诺)。
knex.transaction(Promise.coroutine(function*(t) {
//get books inside the transaction
var authors = yield books.map(x => x.author);
// name should be indexed, this is a single query
var inDb = yield t.select("authors").whereIn("name", authors);
var notIn = authors.filter(author => !inDb.includes("author"));
// now, perform a single multi row insert on the transaction
// I'm assuming PostgreSQL here (return IDs), this is a bit different for SQLite
var ids = yield t("authors").insert(notIn.map(name => {authorName: name });
// update books _inside the transaction_ now with the IDs array
})).then(() => console.log("All done!"));
这样做的好处是只能进行固定数量的查询,并且可能更安全,性能更好。 此外,您的数据库不处于一致状态(尽管您可能必须重试多个实例的操作)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.