[英]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.