簡體   English   中英

思考JavaScript承諾(在這種情況下藍鳥)

[英]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,因為這些數據是標准化的。 作者可能存在也可能不存在,因此我需要:

  • 檢查作者是否存在於DB中
  • 如果是,請使用此ID
  • 否則,插入作者並使用新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'))

問題是獲取作者和創建新作者之間存在競爭條件。 請考慮以下事件順序:

  • 我們試圖為作者B獲得作者A;
  • 讓作者A失敗;
  • 我們要求創建作者A,但它尚未創建;
  • 我們試圖為作者C獲得作者A;
  • 讓作者A失敗;
  • 我們要求創建作者A(再次!);
  • 第一次請求完成;
  • 第二個請求完成;

現在我們在作者表中有兩個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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM