sql >> Database teknologi >  >> NoSQL >> MongoDB

Løsning af resultater med et eksternt API-kald og findOneAndUpdate

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.




  1. Afinstaller Redis på Mac OSX. Ældre version kører efter installation af opdatering

  2. Hvordan opdaterer man værdien af ​​et specifikt indlejret dokument i et array af et specifikt dokument i MongoDB?

  3. Hvordan begrænser jeg CPU- og RAM-ressourcer til mongodump?

  4. MongoDB :Hvordan multipliceres et felt, der kun vises i $project?