MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Zapętlanie wyników z zewnętrznym wywołaniem API i findOneAndUpdate

Podstawową rzeczą, której naprawdę brakuje, jest to, że metody API Mongoose również używają "Obietnice" , ale wydaje się, że po prostu kopiujesz z dokumentacji lub starych przykładów za pomocą wywołań zwrotnych. Rozwiązaniem tego problemu jest przejście na używanie tylko obietnic.

Praca z obietnicami

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

Oprócz ogólnej konwersji z wywołań zwrotnych, główną zmianą jest użycie Promise.all() aby rozwiązać wyjście z Array.map() przetwarzane na wynikach z .find() zamiast for pętla. To właściwie jeden z największych problemów w Twojej próbie, ponieważ for nie może faktycznie kontrolować, kiedy funkcje asynchroniczne zostaną rozwiązane. Innym problemem jest „mieszanie wywołań zwrotnych”, ale to jest to, do czego zwykle się tutaj odnosimy, używając tylko obietnic.

W Array.map( ) zwracamy Promise z wywołania interfejsu API, powiązanego z findOneAndUpdate() który faktycznie aktualizuje dokument. Używamy również new:true aby faktycznie zwrócić zmodyfikowany dokument.

Promise.all() pozwala "tablicy obietnic" rozwiązać i zwrócić tablicę wyników. Widzisz je jako updatedDocs . Kolejną zaletą jest to, że metody wewnętrzne będą strzelać „równolegle”, a nie seryjnie. Zwykle oznacza to szybszą rozdzielczość, choć wymaga to kilku dodatkowych zasobów.

Zauważ również, że używamy "projekcja" { _id:1, tweet:1 } aby zwrócić tylko te dwa pola z Model.find() wynik, ponieważ są to jedyne używane w pozostałych połączeniach. Oszczędza to zwracania całego dokumentu dla każdego wyniku tam, gdy nie używasz innych wartości.

Możesz po prostu zwrócić Promise z findOneAndUpdate() , ale tylko dodaję w console.log() więc możesz zobaczyć, że wyjście jest uruchamiane w tym momencie.

Normalne użycie produkcyjne powinno się bez tego obejść:

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

Inną „poprawką” może być użycie implementacji „bluebird” Promise. map() , które łączą wspólne Array.map() do Promise (s) implementacja z możliwością kontrolowania "współbieżności" równoległych połączeń:

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

Alternatywą do „równoległej” byłaby kolejność wykonywania. Można to rozważyć, jeśli zbyt wiele wyników powoduje zbyt wiele wywołań interfejsu API i wywołań związanych z zapisem z powrotem do bazy danych:

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

Tam możemy użyć Array. zmniejsz() „powiązać” obietnice razem, umożliwiając ich sekwencyjne rozwiązywanie. Zauważ, że tablica wyników jest utrzymywana w zakresie i zamieniana z końcowym .then() dołączany na końcu połączonego łańcucha, ponieważ taka technika jest potrzebna do „zbierania” wyników z obietnic spełnianych w różnych punktach tego „łańcucha”.

Asynchronizacja/Oczekiwanie

W nowoczesnych środowiskach, takich jak NodeJS V8.x, który jest aktualnie aktualną wersją LTS i jest już od jakiegoś czasu, faktycznie masz obsługę async/await . Pozwala to na bardziej naturalne pisanie przepływu

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

Lub nawet przetwarzaj sekwencyjnie, jeśli zasoby stanowią 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)
}

Zauważając również, że findByIdAndUpdate() może być również używany jako dopasowanie _id jest już implikowana, więc nie potrzebujesz całego dokumentu zapytania jako pierwszego argumentu.

Zapis zbiorczy

Na koniec, jeśli w ogóle nie potrzebujesz zaktualizowanych dokumentów w odpowiedzi, bulkWrite() jest lepszą opcją i pozwala na przetwarzanie zapisów na serwerze w jednym żądaniu:

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

Lub przez async/await składnia:

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

Prawie wszystkie kombinacje pokazane powyżej można zmienić w ten sposób, jak bulkWrite() Metoda przyjmuje "tablicę" instrukcji, więc możesz skonstruować tę tablicę z przetworzonych wywołań API z każdej powyższej metody.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Połącz laravel jenssegers z klastrem atlasu mongodb

  2. Alamofire z problemami z połączeniem z hostem lokalnym

  3. MongoDB nie działa z PHP na WAMP

  4. MongoDB operator zapytań typu $

  5. MongoDB:upsert, gdy arrayFilters nie może znaleźć dopasowania