Det centrale, du virkelig mangler, er, at Mongoose API-metoderne også bruger "Løfter" , men det ser ud til, at du bare kopierer fra dokumentation eller gamle eksempler ved hjælp af tilbagekald. Løsningen på dette er at konvertere til kun at bruge Promises.
Arbejd med løfter
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))
Bortset fra den generelle konvertering fra tilbagekald, er den vigtigste ændring at bruge Promise.all()
for at løse outputtet fra Array.map()
bliver behandlet på resultaterne fra .find()
i stedet for for
sløjfe. Det er faktisk et af de største problemer i dit forsøg, siden for
kan faktisk ikke kontrollere, hvornår asynkronfunktionerne løses. Det andet problem er "blanding af tilbagekald", men det er det, vi generelt tager fat på her ved kun at bruge løfter.
Inden for Array.map( )
vi returnerer Promise
fra API-kaldet, kædet til findOneAndUpdate()
som faktisk opdaterer dokumentet. Vi bruger også new:true
for faktisk at returnere det ændrede dokument.
Promise.all()
tillader en "array of Promise" at løse og returnere en række resultater. Disse ser du som updatedDocs
. En anden fordel her er, at de indre metoder vil fyre i "parallel" og ikke i serie. Dette betyder normalt en hurtigere opløsning, selvom det kræver et par flere ressourcer.
Bemærk også, at vi bruger "projektionen" af { _id:1, tweet:1 }
for kun at returnere disse to felter fra Model.find()
resultat, fordi det er de eneste, der bruges i de resterende opkald. Dette sparer på at returnere hele dokumentet for hvert resultat der, når du ikke bruger de andre værdier.
Du kan simpelthen bare returnere løftet
fra findOneAndUpdate()
, men jeg tilføjer bare console.log()
så du kan se, at outputtet udløses på det tidspunkt.
Normal produktionsbrug burde undvære det:
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))
En anden "tweak" kunne være at bruge "bluebird"-implementeringen af Promise. map()
, som begge kombinerer den fælles Array.map()
til Løfte
(s) implementering med evnen til at kontrollere "samtidigheden" af at køre parallelle opkald:
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))
En alternativ til "parallel" ville udføres i rækkefølge. Dette kan overvejes, hvis for mange resultater får for mange API-kald og -kald til at skrive tilbage til databasen:
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))
Der kan vi bruge Array. reduce()
at "kæde" løfterne sammen, så de kan løses sekventielt. Bemærk, at rækken af resultater holdes i omfang og udskiftes med den sidste .then()
tilføjet til enden af den sammenføjede kæde, da du har brug for en sådan teknik til at "samle" resultater fra løfter, der løses på forskellige punkter i den "kæde".
Async/Await
I moderne miljøer fra NodeJS V8.x, som faktisk er den nuværende LTS-udgivelse og har været det i et stykke tid nu, har du faktisk understøttelse af async/await
. Dette giver dig mulighed for at skrive dit flow mere naturligt
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);
}
Eller endda muligvis behandle sekventielt, hvis ressourcer er et problem:
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)
}
Bemærk også, at findByIdAndUpdate()
kan også bruges som matchende _id
er allerede underforstået, så du behøver ikke et helt forespørgselsdokument som et første argument.
BulkWrite
Som en sidste bemærkning, hvis du slet ikke har brug for de opdaterede dokumenter som svar, så bulkWrite()
er den bedre mulighed og tillader, at skrivningerne generelt behandles på serveren i en enkelt anmodning:
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))
Eller via async/await
syntaks:
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);
}
Stort set alle kombinationerne vist ovenfor kan varieres til dette som bulkWrite()
metoden tager en "array" af instruktioner, så du kan konstruere denne matrix ud fra de behandlede API-kald ud af hver metode ovenfor.