Điều cốt lõi mà bạn thực sự thiếu là các phương thức Mongoose API cũng sử dụng " Promise " , nhưng bạn dường như chỉ đang sao chép từ tài liệu hoặc các ví dụ cũ bằng cách sử dụng lệnh gọi lại. Giải pháp cho điều này là chỉ chuyển đổi sang sử dụng Promises.
Làm việc với những lời hứa
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
.then( updated => { console.log(updated); return updated })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Ngoài chuyển đổi chung từ callbacks, thay đổi chính là sử dụng Promise.all()
để giải quyết lỗi từ Array.map()
đang được xử lý dựa trên kết quả từ .find()
thay vì for
vòng. Đó thực sự là một trong những vấn đề lớn nhất trong nỗ lực của bạn, vì for
thực sự không thể kiểm soát khi các chức năng không đồng bộ giải quyết. Vấn đề khác là "trộn các lệnh gọi lại", nhưng đó là những gì chúng tôi thường giải quyết ở đây bằng cách chỉ sử dụng Promises.
Trong Array.map()
chúng tôi trả lại Promise
từ lệnh gọi API, được liên kết với findOneAndUpdate()
mà thực sự đang cập nhật tài liệu. Chúng tôi cũng sử dụng new: true
để thực sự trả lại tài liệu đã sửa đổi.
Promise.all()
cho phép một "mảng Lời hứa" giải quyết và trả về một mảng kết quả. Những thứ bạn thấy là updatedDocs
. Một ưu điểm khác ở đây là các phương thức bên trong sẽ kích hoạt "song song" chứ không phải theo chuỗi. Điều này thường có nghĩa là độ phân giải nhanh hơn, mặc dù cần nhiều tài nguyên hơn.
Cũng xin lưu ý rằng chúng tôi sử dụng "phép chiếu" của { _id: 1, tweet: 1 }
để chỉ trả về hai trường đó từ Model.find()
kết quả bởi vì đó là những cái duy nhất được sử dụng trong các cuộc gọi còn lại. Điều này giúp tiết kiệm việc trả lại toàn bộ tài liệu cho mỗi kết quả ở đó khi bạn không sử dụng các giá trị khác.
Bạn chỉ cần trả về Promise
từ findOneAndUpdate()
, nhưng tôi chỉ thêm vào console.log()
để bạn có thể thấy đầu ra đang kích hoạt tại thời điểm đó.
Việc sử dụng sản xuất thông thường nên thực hiện mà không có nó:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Một "tinh chỉnh" khác có thể là sử dụng triển khai "bluebird" của Promise.map()
, cả hai kết hợp chung Array.map()
tới Promise
(các) triển khai với khả năng kiểm soát "đồng thời" của việc chạy các cuộc gọi song song:
const Promise = require("bluebird");
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.map(tweets, ({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
),
{ concurrency: 5 }
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Một thay thế cho "song song" sẽ được thực thi theo trình tự. Điều này có thể được xem xét nếu quá nhiều kết quả khiến quá nhiều lệnh gọi và lệnh gọi API ghi lại vào cơ sở dữ liệu:
Model.find({},{ _id: 1, tweet: 1}).then(tweets => {
let updatedDocs = [];
return tweets.reduce((o,{ _id, tweet }) =>
o.then(() => api.petition(tweet))
.then(result => TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
.then(updated => updatedDocs.push(updated))
,Promise.resolve()
).then(() => updatedDocs);
})
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
Ở đó, chúng tôi có thể sử dụng Array.reduce()
để "xâu chuỗi" các lời hứa lại với nhau cho phép chúng giải quyết tuần tự. Lưu ý rằng mảng kết quả được giữ trong phạm vi và được hoán đổi bằng .then()
cuối cùng được thêm vào cuối chuỗi đã tham gia vì bạn cần một kỹ thuật như vậy để "thu thập" kết quả từ các Promise giải quyết tại các điểm khác nhau trong "chuỗi" đó.
Không đồng bộ / Đang chờ
Trong các môi trường hiện đại như từ NodeJS V8.x, thực sự là bản phát hành LTS hiện tại và đã có một thời gian, bạn thực sự có hỗ trợ cho async/await
. Điều này cho phép bạn viết quy trình của mình một cách tự nhiên hơn
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let updatedDocs = await Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
)
)
);
// Do something with results
} catch(e) {
console.error(e);
}
Hoặc thậm chí có thể xử lý tuần tự, nếu tài nguyên là vấn đề:
try {
let cursor = Model.collection.find().project({ _id: 1, tweet: 1 });
while ( await cursor.hasNext() ) {
let { _id, tweet } = await cursor.next();
let result = await api.petition(tweet);
let updated = await TweetModel.findByIdAndUpdate(_id, { result },{ new: true });
// do something with updated document
}
} catch(e) {
console.error(e)
}
Cũng cần lưu ý rằng findByIdAndUpdate()
cũng có thể được sử dụng để khớp với _id
đã được ngụ ý nên bạn không cần toàn bộ tài liệu truy vấn làm đối số đầu tiên.
BulkWrite
Lưu ý cuối cùng nếu bạn thực sự không cần các tài liệu cập nhật để đáp ứng, thì hãy bulkWrite()
là tùy chọn tốt hơn và cho phép quá trình ghi thường được xử lý trên máy chủ trong một yêu cầu duy nhất:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)
).then( results =>
Tweetmodel.bulkWrite(
results.map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
)
)
.catch(e => console.error(e))
Hoặc qua async/await
cú pháp:
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let writeResult = await Tweetmodel.bulkWrite(
(await Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)).map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
);
} catch(e) {
console.error(e);
}
Khá nhiều kết hợp hiển thị ở trên có thể được thay đổi thành kết hợp này dưới dạng bulkWrite()
phương thức nhận một "mảng" hướng dẫn, vì vậy bạn có thể tạo mảng đó từ các lệnh gọi API đã xử lý từ mọi phương thức ở trên.