假设您可以并行处理每本书。那么使用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实例。这很糟糕!
为解决此问题,我们可以使用传统方法:锁定。我们需要保留每个作者锁的表。当我们发送创建请求时,我们锁定相应的锁。请求完成后,我们将其解锁。所有涉及相同作者的其他操作都需要先获取锁,然后再执行任何操作。
这似乎很难,但在我们的情况下可以大大简化,因为我们可以使用我们的请求承诺代替锁:
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'))
就是这样!如果一个请求正在处理“作者”,将返回相同的承诺。