MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Schleifen von Ergebnissen mit einem externen API-Aufruf und findOneAndUpdate

Das Wichtigste, was Sie wirklich vermissen, ist, dass die Mongoose-API-Methoden auch "Versprechen" , aber Sie scheinen nur aus der Dokumentation oder alten Beispielen mit Rückrufen zu kopieren. Die Lösung hierfür ist die Umstellung auf die ausschließliche Verwendung von Promises.

Mit Versprechen arbeiten

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

Abgesehen von der allgemeinen Konvertierung von Rückrufen ist die Hauptänderung die Verwendung von Promise.all() um die Ausgabe von aufzulösen Array.map() die auf den Ergebnissen von .find() verarbeitet werden anstelle von for Schleife. Das ist eigentlich eines der größten Probleme bei deinem Versuch, seit dem for kann nicht wirklich steuern, wann die asynchronen Funktionen aufgelöst werden. Das andere Problem ist das "Mischen von Rückrufen", aber das ist es, was wir hier im Allgemeinen ansprechen, indem wir nur Promises verwenden.

Innerhalb der Array.map( ) wir geben das Promise zurück aus dem API-Aufruf, verkettet mit findOneAndUpdate() die eigentlich das Dokument aktualisiert. Wir verwenden auch new:true um das geänderte Dokument tatsächlich zurückzugeben.

Promise.all() ermöglicht es einem "Array of Promise", ein Array von Ergebnissen aufzulösen und zurückzugeben. Diese sehen Sie als updatedDocs . Ein weiterer Vorteil hier ist, dass die inneren Methoden "parallel" und nicht in Reihe feuern. Dies bedeutet in der Regel eine schnellere Auflösung, erfordert jedoch etwas mehr Ressourcen.

Beachten Sie auch, dass wir die „Projektion“ von { _id:1, tweet:1 } verwenden um nur diese beiden Felder aus Model.find() zurückzugeben Ergebnis, da dies die einzigen sind, die in den verbleibenden Anrufen verwendet werden. Dies erspart die Rückgabe des gesamten Dokuments für jedes Ergebnis dort, wenn Sie die anderen Werte nicht verwenden.

Sie könnten einfach das Promise aus findOneAndUpdate() , aber ich füge nur console.log() hinzu damit Sie sehen können, dass die Ausgabe an diesem Punkt ausgelöst wird.

Der normale Produktiveinsatz sollte darauf verzichten:

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

Eine weitere „Optimierung“ könnte darin bestehen, die „Bluebird“-Implementierung von Promise zu verwenden. map() , die beide den gemeinsamen kombinieren Array.map() zu Promise (s) Implementierung mit der Fähigkeit, "Gleichzeitigkeit" von laufenden parallelen Aufrufen zu steuern:

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

Eine Alternative zu "parallel" wäre die Ausführung nacheinander. Dies kann in Betracht gezogen werden, wenn zu viele Ergebnisse zu viele API-Aufrufe und Aufrufe zum Zurückschreiben in die Datenbank verursachen:

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

Dort können wir Array verwenden. Reduce() um die Versprechungen miteinander zu "verketten", damit sie nacheinander aufgelöst werden können. Beachten Sie, dass das Array der Ergebnisse im Gültigkeitsbereich bleibt und mit dem abschließenden .then() ausgetauscht wird an das Ende der verbundenen Kette angehängt, da Sie eine solche Technik benötigen, um Ergebnisse von Promises zu "sammeln", die an verschiedenen Punkten in dieser "Kette" aufgelöst werden.

Asynchron/Warten

In modernen Umgebungen ab NodeJS V8.x, das eigentlich die aktuelle LTS-Version ist und das schon seit einiger Zeit, haben Sie tatsächlich Unterstützung für async/await . Dadurch können Sie Ihren Flow natürlicher schreiben

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

Oder möglicherweise sogar nacheinander verarbeiten, wenn Ressourcen ein Problem sind:

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

Beachten Sie auch, dass findByIdAndUpdate() kann auch als Übereinstimmung mit der _id verwendet werden ist bereits impliziert, sodass Sie kein ganzes Abfragedokument als erstes Argument benötigen.

BulkWrite

Als letzte Anmerkung, wenn Sie die aktualisierten Dokumente überhaupt nicht als Antwort benötigen, dann bulkWrite() ist die bessere Option und ermöglicht, dass die Schreibvorgänge im Allgemeinen in einer einzigen Anfrage auf dem Server verarbeitet werden:

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

Oder über 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);
}

So ziemlich alle oben gezeigten Kombinationen können als bulkWrite() variiert werden. Die Methode nimmt ein "Array" von Anweisungen, sodass Sie dieses Array aus den verarbeiteten API-Aufrufen aus jeder oben genannten Methode erstellen können.