Hal inti yang benar-benar Anda lewatkan adalah bahwa metode API Mongoose juga menggunakan "Janji" , tetapi Anda tampaknya hanya menyalin dari dokumentasi atau contoh lama menggunakan panggilan balik. Solusi untuk ini adalah mengonversi untuk menggunakan Janji saja.
Bekerja dengan Janji
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))
Selain konversi umum dari panggilan balik, perubahan utama adalah menggunakan Promise.all()
untuk menyelesaikan keluaran dari Array.map()
sedang diproses pada hasil dari .find()
alih-alih for
lingkaran. Itu sebenarnya salah satu masalah terbesar dalam usaha Anda, karena for
tidak dapat benar-benar mengontrol kapan fungsi async diselesaikan. Masalah lainnya adalah "mencampur callback", tetapi itulah yang biasanya kami bahas di sini dengan hanya menggunakan Promises.
Dalam Array.map()
kami mengembalikan Promise
dari panggilan API, dirantai ke findOneAndUpdate()
yang sebenarnya memperbarui dokumen. Kami juga menggunakan new: true
untuk benar-benar mengembalikan dokumen yang dimodifikasi.
Promise.all()
memungkinkan "array of Promise" untuk menyelesaikan dan mengembalikan array hasil. Ini Anda lihat sebagai updatedDocs
. Keuntungan lain di sini adalah bahwa metode dalam akan diaktifkan secara "paralel" dan tidak secara seri. Ini biasanya berarti resolusi yang lebih cepat, meskipun membutuhkan lebih banyak sumber daya.
Perhatikan juga bahwa kita menggunakan "proyeksi" dari { _id: 1, tweet: 1 }
untuk hanya mengembalikan dua bidang tersebut dari Model.find()
hasil karena itu adalah satu-satunya yang digunakan dalam panggilan yang tersisa. Ini menghemat pengembalian seluruh dokumen untuk setiap hasil di sana saat Anda tidak menggunakan nilai lainnya.
Anda cukup mengembalikan Promise
dari findOneAndUpdate()
, tapi saya hanya menambahkan console.log()
sehingga Anda dapat melihat output menyala pada saat itu.
Penggunaan produksi normal harus dilakukan tanpanya:
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))
"Tweak" lain bisa menggunakan implementasi "bluebird" dari Promise.map()
, yang keduanya menggabungkan Array.map()
ke Promise
(s) implementasi dengan kemampuan untuk mengontrol "konkurensi" menjalankan panggilan paralel:
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))
Alternatif untuk "paralel" akan dieksekusi secara berurutan. Ini mungkin dipertimbangkan jika terlalu banyak hasil menyebabkan terlalu banyak panggilan API dan panggilan untuk menulis kembali ke database:
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))
Di sana kita dapat menggunakan Array.reduce()
untuk "merantai" janji bersama yang memungkinkan mereka untuk menyelesaikan secara berurutan. Perhatikan bahwa larik hasil disimpan dalam cakupan dan ditukar dengan .then()
final terakhir ditambahkan ke akhir rantai gabungan karena Anda memerlukan teknik seperti itu untuk "mengumpulkan" hasil dari Promise yang diselesaikan di berbagai titik dalam "rantai" tersebut.
Asinkron/Menunggu
Di lingkungan modern seperti dari NodeJS V8.x yang sebenarnya merupakan rilis LTS saat ini dan sudah lama, Anda sebenarnya memiliki dukungan untuk async/await
. Ini memungkinkan Anda untuk menulis alur Anda secara lebih alami
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);
}
Atau bahkan mungkin memproses secara berurutan, jika sumber daya menjadi masalah:
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)
}
Perhatikan juga bahwa findByIdAndUpdate()
juga dapat digunakan sebagai pencocokan _id
sudah tersirat sehingga Anda tidak memerlukan seluruh dokumen kueri sebagai argumen pertama.
Tulis Massal
Sebagai catatan terakhir jika Anda sama sekali tidak memerlukan dokumen yang diperbarui sebagai tanggapan, maka bulkWrite()
adalah opsi yang lebih baik dan memungkinkan penulisan untuk diproses secara umum di server dalam satu permintaan:
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))
Atau melalui async/await
sintaks:
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);
}
Hampir semua kombinasi yang ditunjukkan di atas dapat divariasikan menjadi ini sebagai bulkWrite()
metode mengambil "array" instruksi, sehingga Anda dapat membuat larik tersebut dari panggilan API yang diproses dari setiap metode di atas.