sql >> Database >  >> NoSQL >> MongoDB

Resultaten herhalen met een externe API-aanroep en findOneAndUpdate

Het belangrijkste dat je echt mist, is dat de Mongoose API-methoden ook gebruik maken van "Belofte" , maar je lijkt gewoon te kopiëren uit documentatie of oude voorbeelden met behulp van callbacks. De oplossing hiervoor is om te converteren naar het gebruik van alleen Beloften.

Werken met beloften

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

Afgezien van de algemene conversie van callbacks, is de belangrijkste wijziging het gebruik van Promise.all() om de uitvoer van de op te lossen Array.map() wordt verwerkt op de resultaten van .find() in plaats van de voor lus. Dat is eigenlijk een van de grootste problemen bij je poging, aangezien de for kan niet echt bepalen wanneer de asynchrone functies worden opgelost. Het andere probleem is "callbacks mixen", maar dat is wat we hier over het algemeen aanpakken door alleen beloften te gebruiken.

Binnen de Array.map( ) we retourneren de Belofte van de API-aanroep, geketend aan de findOneAndUpdate() die het document feitelijk bijwerkt. We gebruiken ook new:true om het gewijzigde document daadwerkelijk terug te sturen.

Promise.all() staat een "array of Promise" toe om een ​​reeks resultaten op te lossen en te retourneren. Deze zie je als updatedDocs . Een ander voordeel hier is dat de innerlijke methoden "parallel" zullen vuren en niet in serie. Dit betekent meestal een snellere oplossing, hoewel er wat meer middelen voor nodig zijn.

Merk ook op dat we de "projectie" van { _id:1, tweet:1 } gebruiken om alleen die twee velden te retourneren van de Model.find() resultaat omdat dat de enige zijn die in de resterende gesprekken worden gebruikt. Dit bespaart u het hele document voor elk resultaat daar terug te sturen als u de andere waarden niet gebruikt.

U kunt gewoon de Promise van de findOneAndUpdate() , maar ik voeg alleen de console.log() . toe zodat je kunt zien dat de output op dat moment wordt geactiveerd.

Normaal productiegebruik zou het zonder moeten doen:

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

Een andere "tweak" zou kunnen zijn om de "bluebird" implementatie van Promise te gebruiken. kaart() , die beide de gemeenschappelijke combineert Array.map() naar Belofte (s) implementatie met de mogelijkheid om "gelijktijdigheid" van het uitvoeren van parallelle oproepen te controleren:

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

Een alternatief voor "parallel" zou in volgorde worden uitgevoerd. Dit kan worden overwogen als te veel resultaten ertoe leiden dat er te veel API-aanroepen en -aanroepen worden teruggeschreven naar de 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))

Daar kunnen we Array gebruiken. reduce() om de beloften aan elkaar te "ketenen", zodat ze achtereenvolgens kunnen worden opgelost. Merk op dat de reeks resultaten binnen het bereik wordt gehouden en verwisseld met de laatste .then() toegevoegd aan het einde van de samengevoegde keten, omdat je een dergelijke techniek nodig hebt om resultaten te "verzamelen" van beloften die op verschillende punten in die "keten" worden opgelost.

Async/wachten

In moderne omgevingen vanaf NodeJS V8.x, wat eigenlijk de huidige LTS-release is en dat al een tijdje is, heb je eigenlijk ondersteuning voor async/wait . Hierdoor kunt u uw stroom natuurlijker schrijven

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

Of zelfs mogelijk opeenvolgend verwerken, als middelen een probleem zijn:

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

Merk ook op dat findByIdAndUpdate() kan ook worden gebruikt als match met de _id is al geïmpliceerd, dus je hebt niet een heel zoekdocument nodig als eerste argument.

BulkWrite

Als laatste opmerking, als u de bijgewerkte documenten helemaal niet nodig heeft, bulkWrite() is de betere optie en zorgt ervoor dat de schrijfbewerkingen over het algemeen in een enkel verzoek op de server kunnen worden verwerkt:

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

Of via async/wait syntaxis:

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

Vrijwel alle hierboven getoonde combinaties kunnen hierin worden gevarieerd, zoals de bulkWrite() methode heeft een "array" met instructies nodig, zodat je die array kunt construeren op basis van de verwerkte API-aanroepen van elke bovenstaande methode.




  1. mongodb geschatte tekenreeksovereenkomst

  2. 2 documenten worden samengevoegd in MongoDB

  3. MongoDB countDocuments()

  4. installatie mongodb-10gen mislukt met apt-get