質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.72%

NodejsでPromise.allの.thenとその後の同期処理の実行順を保証したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,213

mao999

score 95

Node.jsとmysqlモジュールでサーバサイドのプログラムを作成しています。

やりたいこと

for文の中にMySQLの問い合わせ処理をいくつか書き、
その結果を用いて配列を作成したい。
※SQL問い合わせのそれぞれの結果レコードは必ず1件になります。
mysqlクライアントを用いてクエリを事前に確認しています。

分かったこと

・MySQLの問い合わせ処理は、mysqlモジュールを使って非同期処理で行われる。
・for文の中で非同期処理をしたい場合、Promiseを使用して、for文の外で結果を取る。
・Promise.allの.thenでプロミス全てが完了した時に処理を行える。

問題点

  • Promiseと即時処理/同期処理の実行順を保証できない
    SQL文の結果を全て取得し終わってから配列を作成したいのに、
    まだPromiseが完了していないのに最後のfor文が実行されてしまって、
    SQLの結果が反映されない。
    Promise.allと最後のfor文の間に、Promise.allが全て終わるまで待つようなブロッキングをする方法は無いのでしょうか。
  • 2次元配列が直感と違う
    javascriptでは2次元配列をサポートしていないらしく、
    var array = [[1,2],[3,4],[5,6]];var ar1 = array[0];var i = ar1[0];の様にアクセスすればそれらしい事は出来るみたいですが、
    下記ソースコードPromise.all([promisesA]).then(~~内のresults.lengthsqlresults.lengthの値が逆なのではないか?
    直感では、results.length=31と、SQLの結果sqlresults.length=1ですが、
    console.logで出力するとresults.length=1sqlresults.length=31が返ってきます。
    何故でしょうか。

宜しくお願い致します。

ソースコード

少し長くなります。

//SQLでAValueを得るための処理
var promisesA = [];
var resultsA = [];
function getA(arg){
    new Promise(function(resolve,reject){
        var query_sql = connection.query({
            sql : "~~~~",   //略
            timeout: 3000,
            values : [~~~~] //略
        },function(error,results,fields){
            if(error){
                reject(error,query_sql);
                return;
            }
            resolve(results);
        });
    });
}
//SQLでBValueを得るための処理
var promisesB = [];
var resultsB = [];
function getB(arg){
    new Promise(function(resolve,reject){
        var query_sql = connection.query({
            sql : "~~~~",   //略
            timeout: 3000,
            values : [~~~~] //略
        },function(error,results,fields){
            if(error){
                reject(error,query_sql);
                return;
            }
            resolve(results);
        });
    });
}
//SQLでCValueを得るための処理
var promisesC = [];
var resultsC = [];
function getC(arg){
    new Promise(function(resolve,reject){
        var query_sql = connection.query({
            sql : "~~~~",   //略
            timeout: 3000,
            values : [~~~~] //略
        },function(error,results,fields){
            if(error){
                reject(error,query_sql);
                return;
            }
            resolve(results);
        });
    });
}
for(var i=0; i < 31; i++){
   promisesA.push(getA(~~~));
   promisesB.push(getB(~~~));
}
Promise.all([promisesA])
    .then(function(results){
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsA.push(sqlresults[0].AValue);
            if(sqlresults[0].AValue > 0){
                promisesC.push(getC(~~~));
            }
        }
    })
    .catch(function(error,query){
    });
Promise.all([promisesB])
    .then(function(results){
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsB.push(sqlresults[0].BValue);
        }
    })
    .catch(function(error,query){
    });
Promise.all([promisesC])
    .then(function(results){
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsC.push(sqlresults[0].CValue);
        }
    })
    .catch(function(error,query){
    });
//SQL問い合わせの結果をもとに配列を作成
for(var i=0; i < 31; i++){
 //配列作成
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

見直すべき点はあるような気はしますが、ご参考までにとりあえず思いついた回答を。
Node.js 4.4.3です。
Promisesは見当たらなかったのでPromiseにしてます。あと getX は returnするようにしています。

配列どうこうは、allに配列(そもそもpromisesAが配列なのに更に配列にしている)渡しているからじゃないでしょうか。
それを除けば、こう渡せば当然こうとれるという私の感覚とは反してませんでした。

Promiseの結果をとりたいならthenでやるべきでしょう。ということで変更したところ:

  • Promises.all([promisesA]) は Promise.all(promisesA)という形に変えています。
  • A->C->B-> 結果 にしています。(なのでAとBは同時には走りません)
  • SQLの実行はsetTimeoutに変えています。

Promiseの終わりでPromiseを返し、結果修正のところまでthenでつないでいます。
setTimeoutでresolveしているのをその後のPromiseで利用する形に合わせてますので、thenの中はあまり変えてないつもりです。

//SQLでAValueを得るための処理
var promisesA = [];
var resultsA = [];
function getA(arg){
    var v = arg;
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve([{AValue: (arg + 1) * 100 + arg}]);
        }, 100);
    });
}
//SQLでBValueを得るための処理
var promisesB = [];
var resultsB = [];
function getB(arg){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve([{BValue: 'B:' + arg}]);
        }, 400);
    });
}
//SQLでCValueを得るための処理
var promisesC = [];
var resultsC = [];
function getC(arg){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve([{CValue: 'C:' + arg}]);
        }, 50);
    });
}
for(var i=0; i < 31; i++){
    promisesA.push(getA(i));
    promisesB.push(getB(i));
}

Promise.all(promisesA)
    .then(function(results){
        // A
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsA.push(sqlresults[0].AValue);
            if(sqlresults[0].AValue > 0){
                promisesC.push(getC(sqlresults[0].AValue));
            }
        }
        return Promise.all(promisesC);
    })
    .then(function(results){
        // C
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsC.push(sqlresults[0].CValue);
        }

        return Promise.all(promisesB);
    })
    .then(function(results){
        // B
        for(var i=0; i < results.length; i++){
            var sqlresults = results[i];
            resultsB.push(sqlresults[0].BValue);
        }
    })
    .then(function(results){
        for(var i=0; i< 31; i++){
            console.log(resultsA[i]);
            console.log(resultsB[i]);
            console.log(resultsC[i]);
        }
    })
    .catch(function(error,query){
    });

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/09 23:23 編集

    ご回答有難うございます!
    Node.jsのバージョンはv6.3.1でした。

    PromisesはPromiseの間違いでした。修正しておきます。

    仰るとおり、見なおすべき点は色々あるはずです。
    javascriptで書くのは初めてなので、非同期処理をする時の定石/ベストプラクティスの様なものはまだ殆ど知りません。

    配列の件、確かに仰る通りです。
    こう渡せば、こう取れますよね。
    うっかり見逃していました。

    ソースコード書いて頂いて有難うございます。
    後で確認しますので、またコメントします。

    キャンセル

  • 2016/08/10 01:36

    ・getXでreturnする
    ・Promises.all([promisesA]) は Promise.all(promisesA)に修正
    ・Promiseの終わりでPromiseをreturnし、結果修正のところまでthenでつなぐ
    の変更を加えた結果、
    所望の動作をさせる事が出来ました。

    有難うございました。

    キャンセル

  • 2016/08/10 01:52

    動いたようで良かったです。
    Promiseは同期処理との区分けがなかなか難しいですが頑張ってください。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.72%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる