sql >> データベース >  >> NoSQL >> MongoDB

外部API呼び出しとfindOneAndUpdateを使用した結果のループ

    あなたが本当に見逃している中心的なことは、MongooseAPIメソッドも「約束」 、しかし、コールバックを使用してドキュメントや古い例からコピーしているようです。これに対する解決策は、Promiseのみを使用するように変換することです。

    約束の処理

    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の代わりに ループ。 for 以来、これは実際にあなたの試みにおける最大の問題の1つです。 非同期関数がいつ解決されるかを実際に制御することはできません。もう1つの問題は「コールバックの混合」ですが、ここでは通常、Promiseのみを使用して対処しています。

    Array.map( ) Promiseを返します API呼び出しから、 findOneAndUpdate()にチェーンされます これは実際にドキュメントを更新しています。 new:trueも使用します 変更されたドキュメントを実際に返します。

    Promise.all() 「Promiseの配列」が結果の配列を解決して返すことを可能にします。これらはupdatedDocsとして表示されます 。ここでのもう1つの利点は、内部メソッドが直列ではなく「並列」で起動することです。これは通常、より高速な解決を意味しますが、さらにいくつかのリソースが必要になります。

    {_id:1、tweet:1}の「プロジェクション」を使用していることにも注意してください。 Model.find()からこれら2つのフィールドのみを返す 残りの呼び出しで使用されるのはこれらだけであるため、結果が得られます。これにより、他の値を使用しない場合に、結果ごとにドキュメント全体を返す手間が省けます。

    Promise<を返すだけです。 / code> 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))
    

    もう1つの「微調整」は、Promiseの「bluebird」実装を使用することです。 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を使用できます。 reduce() 約束を「連鎖」させて、順番に解決できるようにします。結果の配列はスコープ内に保持され、最後の .then()とスワップアウトされることに注意してください その「チェーン」のさまざまなポイントで解決するPromiseからの結果を「収集」するためのこのような手法が必要なため、結合されたチェーンの最後に追加されます。

    非同期/待機

    実際には現在のLTSリリースであり、しばらく前から存在しているNodeJS V8.x以降の最新の環境では、実際には 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

    最後に、実際に更新されたドキュメントがまったく必要ない場合は、 bullkWrite() はより適切なオプションであり、通常、書き込みを1回のリクエストでサーバー上で処理できます。

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

    bullkWrite()として、上記のほとんどすべての組み合わせをこれに変えることができます。 メソッドは命令の「配列」を受け取るため、上記のすべてのメソッドから処理されたAPI呼び出しからその配列を構築できます。




    1. 自動配線されたRedisTemplate<String、Long>

    2. Javaでの挿入の失敗を処理するMongoDB

    3. MongoDB-大量のMongoCleanerスレッド

    4. MongoDb C#GeoNearクエリ構築