sql >> Databasteknik >  >> NoSQL >> MongoDB

Looping resultat med ett externt API-anrop och findOneAndUpdate

Kärnan du verkligen saknar är att Mongoose API-metoderna också använder "Löfter" , men du verkar bara kopiera från dokumentation eller gamla exempel genom att använda återuppringningar. Lösningen på detta är att konvertera till att endast använda Promises.

Arbeta med löften

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))

Bortsett från den allmänna konverteringen från återuppringningar är den huvudsakliga förändringen att använda Promise.all() för att lösa utdata från Array.map() bearbetas på resultaten från .find() istället för för slinga. Det är faktiskt ett av de största problemen i ditt försök, eftersom for kan faktiskt inte kontrollera när asynkronfunktionerna löser sig. Den andra frågan är att "blanda återuppringningar", men det är vad vi generellt tar upp här genom att bara använda löften.

Inom Array.map( ) vi returnerar Löftet från API-anropet, kopplat till findOneAndUpdate() som faktiskt uppdaterar dokumentet. Vi använder också new:true för att faktiskt returnera det ändrade dokumentet.

Promise.all() tillåter en "array of Promise" att lösa och returnera en rad resultat. Dessa ser du som updatedDocs . En annan fördel här är att de inre metoderna kommer att avfyras "parallellt" och inte i serie. Detta innebär vanligtvis en snabbare upplösning, även om det kräver lite mer resurser.

Observera också att vi använder "projektionen" av { _id:1, tweet:1 } att endast returnera dessa två fält från Model.find() resultat eftersom det är de enda som används i de återstående samtalen. Detta sparar på att returnera hela dokumentet för varje resultat där när du inte använder de andra värdena.

Du kan helt enkelt bara returnera löftet från findOneAndUpdate() , men jag lägger bara till i console.log() så du kan se att utgången avfyras vid den punkten.

Normal produktionsanvändning bör klara sig utan 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 annan "tweak" kan vara att använda "bluebird"-implementeringen av Promise. map() , som båda kombinerar den vanliga Array.map() till Löfte (s) implementering med förmågan att kontrollera "samtidighet" av att köra parallella samtal:

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))

Ett alternativ till "parallell" skulle köras i sekvens. Detta kan övervägas om för många resultat gör att för många API-anrop och anrop skrivs tillbaka till 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))

Där kan vi använda Array. reduce() att "kedja" ihop löftena så att de kan lösas sekventiellt. Observera att uppsättningen av resultat hålls i omfattning och byts ut med den sista .then() läggs till i slutet av den sammanfogade kedjan eftersom du behöver en sådan teknik för att "samla" resultat från löften som löses vid olika punkter i den "kedjan".

Async/Await

I moderna miljöer från och med NodeJS V8.x som faktiskt är den nuvarande LTS-utgåvan och har varit det ett tag nu, har du faktiskt stöd för async/await . Detta gör att du kan skriva ditt flöde mer 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 till och med möjligen bearbeta sekventiellt, om resurser är ett 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)
}

Notera också att findByIdAndUpdate() kan också användas för att matcha _id är redan underförstådd så du behöver inte ett helt frågedokument som ett första argument.

BulkWrite

Som en sista notering om du faktiskt inte behöver de uppdaterade dokumenten som svar alls, code>bulkWrite() är det bättre alternativet och låter skrivningarna i allmänhet bearbetas på servern i en enda begäran:

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 syntax:

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);
}

I stort sett alla kombinationer som visas ovan kan varieras till detta som bulkWrite() Metoden tar en "array" av instruktioner, så du kan konstruera den matrisen från de bearbetade API-anropen från varje metod ovan.




  1. Hur jag skrev en Chart-Topping-app på en vecka med Realm och SwiftUI

  2. Sätt gränser för mongo db-samling

  3. Mongo komplex sortering?

  4. AOF och RDB säkerhetskopior i redis