Ponz Dev Log

ソース派のシステムエンジニアの開発日記

Mongooseの処理が非同期であるのを忘れてはいけない(戒め)

Node.jsのMongoDBのORMといえば、Mongoose。APIドキュメントをよく読んで使用されている方ならお分かりかと思いますが、よく使う.save(ドキュメント保存)や.find (ドキュメント検索)といった処理は非同期で実行されます。
これ、意識してないと処理はあってるのに値がundifinedになってハマる(実際ハマった)ので、非同期で処理されるという前提を頭に置こう(戒め)というメモです。

一応MongooseのPromiseのページにしれっと以下のように書いてあります。

Mongoose Promises v5.0.0-rc1

Built-in Promises
Mongoose async operations, like .save() and queries, return ES6 promises. This means that you can do things like MyModel.findOne({}).then() and await MyModel.findOne({}).exec() (if you're using async/await.
Queries are not promises
Mongoose queries are not promises. However, they do have a .then() function for yield and async/await. If you need a fully-fledged promise, use the .exec() function.

日本語で要約すると、以下のようになるかと。

  • .save()や query(find() / findOne() / etc...) といった 非同期の処理 は、ES6のPromiseオブジェクトを返します。

    • これらのメソッドは .then()を続けられる。
    • async / await 句をつけて実行できる。
  • ただし、queryは厳密にはPromiseではない。

    • .then(), async / await 句を使える。
    • 厳密にPromiseオブジェクトを使いたい場合は .exec()を使おう。

非同期であることはもちろん、返却されるのがPromiseオブジェクト(的なもの)であることもポイントです。 素直に配列が返却されると思いきや、オブジェクトが返ってくるので軽くビビります。

以下のようなコードが非同期で処理されるOK / NGパターンかな。

[NGの例]

 /**
  * findRcd(filterKeys)
  * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。
  * @param {Object} filterKeys - 更新するレコードの検索キー
  * @return {Array} resultSet - 検索結果
  */
findRcd(filterKeys) {
    knmKanr.find(filterKeys, (err, awesomeRecords) => {
 
    log.debug(`FilterKeys: ${JSON.stringify(filterKeys)}`);

    // レコード検索失敗
    if (err) {
        throw new Error("Find Docs Error");  // エラーを投げて処理を中断
    }

    // レコード検索成功
    log.debug(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`);

    return awesomeRecord;    // ここで結果の配列が返却される
});

const searchResultSet = findRcd({ flg : '1' });    // 非同期で処理完了前に、変数に値が格納される
console.log(JSON.stringify(searchResultSet));    // 処理が未解決のため、undefined になる

[OKの例]

  /**
   * findRcd(filterKeys)
   * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。
   * @param {Object} filterKeys - 更新するレコードの検索キー
   * @return {Promise} resultSet - 検索結果
   */
  async findRcd(filterKeys) {
    const resultSet = await knmKanr.find(filterKeys, (err, awesomeRecords) => {
 
     console.log(`FilterKeys: ${JSON.stringify(filterKeys)}`);

        // レコード検索失敗
        if (err) {
            throw new Error("Find Docs Error");  // エラーを投げて処理を中断
        }

        // レコード検索成功
        console.log(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`);

    });

    console.log(`resultSet: ${resultSet}`);    
    return resultSet;    // ここでPromiseオブジェクトが返却される
  }
}

findRcd(query)
    .then((searchResultSet) => {
        console.log(`searchResultSet: ${JSON.stringify(searchResultSet)}`);    // {...} 非同期処理が解決されてから値がセットされる
    }

こういう時こそ、async / await が効果的なんだってわかりますね。 改めて、Mongooseの処理が非同期であるのを忘れてはいけないです(戒め)。