MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Цикъл на резултатите с външно извикване на API и findOneAndUpdate

Основното нещо, което наистина пропускате, е, че методите на API на Mongoose също използват "Обещания" , но изглежда просто копирате от документация или стари примери, като използвате обратни извиквания. Решението за това е да преминете към използване само на Promises.

Работа с обещания

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

Освен общото преобразуване от обратни извиквания, основната промяна е използването на Promise.all() за разрешаване на изхода от Array.map() се обработва върху резултатите от .find() вместо за цикъл. Това всъщност е един от най-големите проблеми във вашия опит след for всъщност не може да контролира кога асинхронните функции се разрешават. Другият проблем е „смесването на обратни извиквания“, но това е, което обикновено разглеждаме тук, като използваме само Promises.

В рамките на Array.map( ) връщаме обещанието от извикването на API, свързано към findOneAndUpdate() което всъщност актуализира документа. Ние също използваме new:true за действително връщане на променения документ.

Promise.all() позволява на "масив от Promise" да разреши и върне масив от резултати. Виждате ги като updatedDocs . Друго предимство тук е, че вътрешните методи ще се задействат "паралелно", а не последователно. Това обикновено означава по-бърза резолюция, въпреки че отнема малко повече ресурси.

Имайте предвид също, че използваме „проекцията“ на { _id:1, tweet:1 } да върне само тези две полета от Model.find() резултат, защото те са единствените, използвани в останалите повиквания. Това спестява връщането на целия документ за всеки резултат там, когато не използвате другите стойности.

Можете просто да върнете Promise от findOneAndUpdate() , но просто добавям в console.log() така че можете да видите, че изходът се задейства в тази точка.

Нормалната производствена употреба трябва да мине без него:

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

Друга „ощипване“ може да бъде използването на внедряването на „bluebird“ на Promise. map() , който комбинира общия Array.map() до Promise (s) внедряване с възможност за контролиране на "едновременността" на изпълнение на паралелни повиквания:

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

Алтернатива на "паралелен" ще бъде изпълнение в последователност. Това може да се има предвид, ако твърде много резултати причиняват твърде много API извиквания и извиквания за обратно записване в базата данни:

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

Там можем да използваме Array. намали() да „свърже във верига“ обещанията заедно, позволявайки им да се решават последователно. Обърнете внимание, че масивът от резултати се запазва в обхвата и се разменя с крайния .then() добавен към края на обединената верига, тъй като имате нужда от такава техника за „събиране“ на резултати от разрешаване на Promises в различни точки от тази „верига“.

Async/Await

В модерни среди като от NodeJS V8.x, което всъщност е текущата LTS версия и е от известно време, всъщност имате поддръжка за async/await . Това ви позволява да пишете своя поток по-естествено

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

Или дори евентуално да обработвате последователно, ако ресурсите са проблем:

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

Отбелязвайки също, че findByIdAndUpdate() може също да се използва като съвпадение на _id вече се подразбира, така че нямате нужда от цял ​​документ на заявка като първи аргумент.

Групово писане

Като последна бележка, ако изобщо не се нуждаете от актуализираните документи в отговор, тогава bulkWrite() е по-добрият вариант и позволява записите като цяло да се обработват на сървъра в една заявка:

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

Или чрез async/await синтаксис:

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

Почти всички комбинации, показани по-горе, могат да бъдат променени в това като bulkWrite() методът приема "масив" от инструкции, така че можете да конструирате този масив от обработените API извиквания от всеки метод по-горе.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. PHP72 MongoDB драйвер с Homebrew на OSX

  2. MongoDB $isoWeek

  3. Mongoose индекс на поле във вложен документ

  4. Как да използвате $push модификатор за актуализиране в MongoDB и C#, когато актуализирате масив в документ

  5. MongoDB bind_ip няма да работи, освен ако не е зададен на 0.0.0.0