質問: forループの中でmysqlクエリを同期的に実行したいのですが、いい方法が思いつきません。 ``` //既に上流のclient.queryでrecordSetは取得済み for( var i=0 ; i < recordSet.length ; i++ ) { client.query( "SELECT * FROM tablename WHERE foo=?",[recordSet[i].nanika],function(e,r,f){ console.log(i+"/"+r[0].length ); }); } ``` (サンプルなのでクエリが非同期じゃなくてもいいじゃないな感じですが、本来は同期処理が必要なクエリです。) asyncを使って非同期処理を同期的に処理をしたことは有りますが、それではうまくいきませんでした。 なにかヒントで構いませんので、教えていただけますとありがたいです。
回答: Promiseを使う方法です。 ``` // チェーン開始用の最初のプロミス var p = new Promise(function(res) { res(); }); for(var i = 0; i < recordSet.length; i++) { // 変数は同じやつに代入する p = p.then(makePromiseFunc(i)); } p.then(function() { // 全部終わってから何かするならこのように console.log("complete!"); }); // then()に渡す関数を返す関数 function makePromiseFunc(idx) { return function(prevValue) { return new Promise(function(res, rej) { client.query( "SELECT * FROM tablename WHERE foo=?", [recordSet[idx].nanika], function(e, r, f) { console.log("prev: " + prevValue); console.log(i + "/" + r[0].length); res(i + "/" + r[0].length); }); }); }; } ``` **「チェーン開始用の最初のプロミス」**は何もしない必ず成功するプロミスを設定してますが、pが常にthenableなオブジェクト(=Promise)であるようにするにはこういったダミープロミスを作成するのが簡単だと思います。 **「変数は同じやつに代入する」**ってとこはえっ本当に大丈夫なの?と思うかもしれませんが、変数pは「前の処理が終わったことのプロミス」なので「前の処理」のことは覚えてます。再帰的に最初から全部覚えてるので大丈夫です(あんま詳しい内部の実装上の話は知りません)。 そして**「全部終わってから何かするならこのように」**はそのままの意味です。ループで設定した処理の最後のやつが終わったならば(then)という処理です。 最後に**「then()に渡す関数を返す関数」**ですが、これを別に作ってあるのはループ中にfunction宣言するとエディタの構文チェックに怒られるからです...。まあバグの元になるのでこのようにクロージャを作った方がいいです。 then()の引数はfunctionなのでそれに渡すfunctionを返しています。そしてそのfunctionでは非同期処理の終了を約束するPromiseを返しています。内部の非同期で実行される処理の最後で`res()`(これは関数です。resolveの略でプロミスのコンストラクタに渡す関数の第一引数で指定してます。)を呼び出すのを忘れないでください。 なお上の例では途中でデータ異常等で処理を中断したい場合や他の例外が発生した場合などの処理をはしょってます。自ら例外を投げたい場合は`rej()`(rejectの略です)を実行してください。そしてそれらをハンドリングするために`p.catch(function() {...`を適切な場所に入れるべきです。 いかがでしょうか。