promise初心者です。promiseの記述方法がこれで正しいか分からなかったのでご質問させてください。環境はnode.js v4.3です。
promiseの直列処理のいくつかの処理の中で、配列要素を処理するreduceを追加することはできますか?また、2つ目の質問ですが、下記コード中のfunction taskB_Work(value)
で記述する処理によっては、逐次処理が最後まで進むものの、プロセスが終了しない事がありました。これはどのような原因が考えられますでしょうか?(console.log("complete!");
以下を参照)
主にこのような処理を逐次処理で行いたいと考えております。
- 配列を取得
- 配列要素を元に処理 (配列要素の数だけ逐次処理したい)
- 処理した結果を出力
現在、以下のようなコードを書いていますが、このような形でpromiseの記述は問題ないでしょうか?
参考記事: Qiita | Promiseについて0から勉強してみた
http://qiita.com/toshihirock/items/e49b66f8685a8510bd76
// 配列の取得を行うタスク
function taskA() {
return new Promise(function(resolve, reject) {
var Arr = ["a","b","c","d","e"];
resolve(Arr);
});
}
// taskAで取得した値を加工するタスク
function taskB(arr) {
var editedArr = [];
return new Promise(function(resolve, reject) {
return arr.reduce(function(promise, value) {
return promise.then(function() {
return taskB_Work(value);
}).then(function(editedElement){
editedArr.push(editedElement);
});
}, Promise.resolve() )
.then(
function(){
resolve(editedArr);
},
function(){
reject("taskB 処理に失敗");
}
);
});
}
// taskBのreduceで呼び出され、配列要素に対して行う処理
function taskB_Work(value){
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log("[taskB_Work] value="+value);
resolve(value + "_edited");
}, 2000);
});
}
// taskBで加工した配列の値を出力するタスク
function taskC(editedArr) {
return new Promise(function(resolve, reject) {
console.log("[taskC]:");
console.log(editedArr);
resolve();
});
}
//逐次処理を行う
function sequenceTasks(tasks) {
function recordValue(results, value) {
return value;
}
var pushValue = recordValue.bind(null, []);
return tasks.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
var promises = {
doTaskA: function() {
return taskA().then();
},
doTaskB: function(arr) {
return taskB(arr).then();
},
doTaskC: function(editedArr) {
return taskC(editedArr).then();
}
};
function main() {
return sequenceTasks([ promises.doTaskA, promises.doTaskB, promises.doTaskC ]);
}
main().then(function(value) {
console.log("complete!");
//function taskB_Work(value)`で記述する処理によっては、逐次処理がここまで進むものの、プロセスが終了しない事があります。
}).catch(function(error) {
console.log("error:" + error);
});
taskB_Workの処理内容は実際には以下のようなものを考えています。全体の処理の概要は、
- taskA : 改行区切りのURLが保存されているテキストファイルを開き、URLを配列要素に格納
- taskB : 各配列要素のURLに対して、flickrapiを実行
- taskC : 新たに取得したURLをテキストファイルで出力
という流れです。
// flickrのリサイズ画像URL(inPhotoURL)を元に、オリジナルサイズの画像URLを取得する。
/*
var FlickrAPI = require("flickrapi");
var flickrApiOptions = {
api_key : process.env.api_key,
secret : process.env.secret,
permissions : process.env.permissions,
user_id : process.env.user_id,
access_token : process.env.access_token,
access_token_secret: process.env.access_token_secret
};
*/
function taskB_Work(inPhotoURL){
return new Promise(function(resolve, reject) {
FlickrAPI.authenticate(flickrApiOptions, function(error, flickr) {
var matchPat = inPhotoURL.match(/(https:\/\/farm[0-9]\.staticflickr\.com\/[0-9]+\/)([0-9]+)_([0-9a-z]+)[0-9a-z_]*\.jpg/);
var parameter = {
"api_key" : flickrApiOptions.api_key ,
"photo_id" : matchPat[2],
"secret" : matchPat[3]
};
if(!error){
// getInfoにより、originalsecretとoriginalformatを取得
// (getInfo) https://www.flickr.com/services/api/flickr.photos.getInfo.html
flickr.photos.getInfo(parameter, function(err, result) {
if(err) {
reject("flickr APIの実行に失敗しました : " + err);
}else{
resolve(matchPat[1] + matchPat[2] + "_"+ result.photo.originalsecret +"_o."+ result.photo.originalformat);
}
});
}else{
reject("flickr APIの認証に失敗しました :" + error);
}
});
});
}
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
0
プロセスが終了しない件については再現ができませんでしたので、Promiseの記述方法について回答してみます。
このような形でpromiseの記述は問題ないでしょうか?
同期処理をわざわざ非同期処理に変換しているため無用に複雑なコードになっているという印象を持ちました。また、PromiseをさらにPromiseで囲むような冗長な処理も回避可能です。
promiseの直列処理のいくつかの処理の中で、配列要素を処理するreduceを追加することはできますか?
タイトルにも同様のことが書かれていますが、この質問の意図が分かりませんでした。コードを見ていただくのが早いかもしれません。
ご提示のコードから不要な非同期処理を省いて私なりに書き直してみました。
// 配列の取得を行うタスク
function taskA() {
return ["a","b","c","d","e"];
}
// taskAで取得した値を加工するタスク
function taskB(arr) {
return arr.reduce(function(promise, value) {
return promise.then(function(editedArray) {
return taskB_Work(value).then(function(editedElement) {
editedArray.push(editedElement);
return editedArray;
});
});
}, Promise.resolve([]))
.catch(function() {
throw new Error("taskB 処理に失敗");
});
}
// taskBのreduceで呼び出され、配列要素に対して行う処理
function taskB_Work(value){
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log("[taskB_Work] value="+value);
resolve(value + "_edited");
}, 2000);
});
}
// taskBで加工した配列の値を出力するタスク
function taskC(editedArr) {
console.log("[taskC]:");
console.log(editedArr);
};
function main() {
return taskB(taskA()).then(taskC);
}
main().then(function() {
console.log("complete!");
}).catch(function(error) {
console.log("error:" + error);
});
then()
やcatch()
の中で同期例外を投げるとそのPromiseはreject()
されたことになるので、taskB
の例外処理ではそれを利用しています。Promiseの例外処理についてはこの記事が詳しいです。
ところで、Babelなどのトランスパイラを使えばasync
,await
というPromiseよりも強力な非同期処理の仕組みが利用できますのでそちらを使ってみるのも良いかもしれません。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.13%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
horse_n_deer
2016/06/11 08:05
関数外で状態を保持する意味はありますか?もし、module.exportsなどで関数を提供している場合、複数回の同時実行でArr_editedが壊れると思いますが…。
TEA
2016/06/13 17:04
ご指摘ありがとうございます。状態とは具体的に、配列の値のことでしょうか?関数外で状態を保持する理由は特にありません。promiseの返り値をどのように渡せばいいのか不明でした(特に関数taskB)ので、自分なりに理解できるコードを記述させて頂きました。
指摘後、配列については関数内で定義するよう変更してみました。こちらも含め、質問の内容についてご意見をいただけましたら幸いです。
horse_n_deer
2016/06/13 21:34
「プロセスが終了しない」を再現できる`taskB_Work`の内容を書くことは可能ですか?再現できない以上は、「ファイルの監視やサーバーのlistenなど、中でプロセスが落ちない処理を実行している」という予想くらいしか付かないです。
TEA
2016/06/14 09:58
`taskB_Work`の内容を追記しました。node-flickrapiを利用しているのですが、そのコールバックでreject();している事が問題なのかなと考えていますが、具体的な事が分かりません。taskB_Workは配列要素の数だけ実行され、最後にtaskCが実行されますが、プロセスが落ちない状況となっています。こちらについてご意見をいただけましたら幸いです。
また、promiseの記述方法については、一つ目で示したjavascriptコードのような記述で問題ないでしょうか?promiseに関する記述が原因でプロセスが落ちない原因となっていないかも不明でしたので、差し支えなければこの件につきましてもご意見をお待ちしております。
horse_n_deer
2016/06/15 19:09
念のため、catchできていないPromiseがないか https://nodejs.org/api/process.html#process_event_unhandledrejection かbluebirdで確認してください。
TEA
2016/06/17 09:43
ご返事いただきありがとうございます。npm install bluebirdとvar Promise = require('bluebird');した後で、process.on('unhandledRejection',.. を追加してみましたが、何かのエラーがcatchされる気配はありませんでした。。。