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

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

新規登録して質問してみよう
ただいま回答率
85.35%
並列処理

複数の計算が同時に実行される手法

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

2387閲覧

Promise.allの中でさらにPromise.allするのは正しいか。

Anon_

総合スコア334

並列処理

複数の計算が同時に実行される手法

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2021/07/14 01:29

編集2021/07/14 07:55

Promise.allの中でfor分を使っているのですが、その中でawaitを使用しているため、forの処理が順番に実行されます。
これは当然のことだと思うのですが、このforの処理を並列で処理したいのですがこの場合Promise.allの中でさらにPromise.allを使用する形が正しいのでしょうか?

実際のコードを提示できず、うまく伝えられるか分かりませんが下記のようなことをやろうとしています。
listに複数のURLが格納されていて、そこにfetchでアクセスし、仮に取得できたらそのURLにパラメータを付与して次の情報をfetchで取得ということをやりたいので、Promiseの中でawaitを使用しています。
ただ、1度目のアクセスで必ず情報が取得できるというわけではない特殊な状況のため、同じURLに対して同時に3発リクエストを投げて成功したものがあればあれば即時breakというようなことをやりたいのですが良い方法が思いつきません。

「追記コード」

JS

1var list = [ 2 { 3 url: 'example.com', 4 }, 5 { 6 url: 'sub2.example.com', 7 } 8 ]; 9 10 Promise.all( 11 list.map( async(obj) => { 12 for(let i=0; i<3; i++){ 13 let res = await (await fetch(obj.url)).json(); 14 if(res.success){ 15 let res2 = await (await fetch(obj.url)).json(); 16 if(res2.success){ 17 break; 18 } 19 } 20 } 21 }) 22 );

[修正コード]

var list = [ { url: 'example.com', flag: false, }, { url: 'sub2.example.com', flag: false, } ]; list.map( (obj, index) => { Promise.all( Array(3).fill(null).map( fetch(obj.url) .then( res => { return res.json() }) .then(json => { if(json.success){ list[index].flag = true; } }) .catch( err => { console.log(err); }) ) ).then( data=> if( obj.flag ){ //3発のうちどれかが成功していれば実行 } ).catch( e => { console.log(e); }) })

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2021/07/14 01:54

要件を満たせる(やりたいことが実現できて不具合が起きてない)なら正しい が答えですが、そのあたり、実際やってみてどうだったのでしょうか
guest

回答3

0

すでに回答がついていますが、Promise.anyの記述がないため回答します。

質問者さんが言っているのは以下のような使い方が正しいかではないでしょうか。

js

1Promise.all(array1.map(arg => Promise.all( 2 array2.map(/* fetchする */) 3)))

これは同じもの(Promise.all)を二重に使って無駄なことをしていますし、正しい動作をしません。
Promise.allは引数のPromiseが一つでもrejectされるとrejectされます。
この場合に正しいのはPromise.anyです。
Promise.anyは引数のPromiseが一つでもresolveされるとresolveされます。

js

1Promise.all(array.map(obj => 2 Promise.any( 3 Array(3).fill(null).map(/* fetchする */) 4 ).then(/* 値の加工 */) 5))

Promise - JavaScript | MDN

投稿2021/07/14 08:40

ka2obushi

総合スコア173

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Anon_

2021/07/14 08:52

ありがとうございます。 今ちょうど別の方との議論にあがってきたところです。 ところで、fetchで仮にリクエストに失敗してcatchでrejectしなかったとしても、 Promise.allのcatchが実行されてしまう仕様でしたっけ・・
ka2obushi

2021/07/14 09:33

catchでエラーを処理すれば返り値はfulfilledになるので、Promise.allの他のとこでコケてなければPromise.all().catchは実行されないはずです。
guest

0

ベストアンサー

こんにちは。

コードを拝見して、疑問点がありましたので回答します。


まず、fetch は Responseオブジェクトで解決するPromiseを返すので、偽になることはないと思います。
つまり、404エラーが返ってこようとも、if(res){}などは常に真として分岐するはずです。


次に、「同時に3発リクエストを投げて成功したものがあればあれば即時break」とのことですが。
リクエストは並列に投げるのでしたら、リクエストの成功が確認できた時は3発のリクエストは全て投げ終えてループを抜けているはずです。
ですので、breakのしようがないだろう、と思います。


つまり、forループをやめて、3発のリクエスト(のPromise)を配列に詰めて、Promise.all()に渡せばいいかと思います。
そして、その戻り値であるPromiseを配列に詰めて、再度Promise.all()すればいいのではないでしょうか。

投稿2021/07/14 05:06

Lhankor_Mhy

総合スコア36960

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Anon_

2021/07/14 06:58

promiseの判定については失礼いたしました。 即時breakできない点は、別の方との議論中に気づきましたが見事に指摘されてしまいましたね。 実際のURLは2つではないので全部同時に投げると、メモリリークを起こしそうなので、URLは1つづつ実行して同一URLをPromise.allで同時実行という形に変更しようと思います。
Lhankor_Mhy

2021/07/14 07:22

ご解決されて何よりです。 お手数ですが、自己解決の処理をするか、BAの選出をお願いします。
Anon_

2021/07/14 07:57

本文に修正コードを記載しました。 よければ添削していただけると助かります。 BAは候補が2人いるため迷っているところです。
Lhankor_Mhy

2021/07/14 08:47

あー、でも、そうか、403とかで解決されてしまうと厄介ですね。 どのような目的なのかわからないので何とも言えませんが、Promise.allSettled とかで全部なめた方がいいのかもしれないです。
Anon_

2021/07/14 08:48

fetchのcatchでrejectを呼ばなければ、即Promise.allはこけないとおもっていましたが、そうではなかったですかね。 Promise.anyは初めて知りました。 なんだかこちらの方が綺麗に書けそうですね!
Anon_

2021/07/14 08:49

403でもfetchで取得したデータの中で、分岐をかけてほしい情報がとれたらresolveという形ではダメですかね?
Lhankor_Mhy

2021/07/14 08:53

なるほど、それでよさそうですね。
Anon_

2021/07/14 23:36

ありがとうございました!
Anon_

2021/07/15 08:24

あ、ちなみにmap内部でawaitって効かないんですかね
Lhankor_Mhy

2021/07/15 08:30

引数に渡す関数をasyncにすれば、その中ではawait使えると思いますが。
Anon_

2021/07/15 08:33

と、思ってそのようにしていたのですがループ内の処理を待たずに、次の処理が走ってしまっていたので、mapをfor ofに変更して意図する挙動にすることができました。
Lhankor_Mhy

2021/07/15 08:47

非同期関数を呼び出していますので、どうしてもそうなりますよね。 for of を使う対応も正しいと思います。
Anon_

2021/07/21 08:04

Promise.any( list.map( async(obj)=>{ console.log(obj); let today = new Date(); resolve(today); }) ).then( res=> { console.log('res'); console.log(res); }).catch( e=> { console.log(e); }); こんな感じでobjectをresolveで渡す事ってできないんですかね?
Lhankor_Mhy

2021/07/21 08:26

できると思います。 resolve(today); ではなくて、 return today; では。 (なんとなくXY問題の予感がします)
Anon_

2021/07/21 08:48

thenになげたいのですが、resolveでなくて良いのでしょうか
Lhankor_Mhy

2021/07/21 08:49

「thenになげたい」の意味を読み取れているかどうかわかりませんが、return でいいと思います。 そもそも resolve は定義されていないですよね?
Anon_

2021/08/13 01:03 編集

文字列をこんな感じで投げると resolve(['test']); 下記のようなエラーがでてしまうのですよね。 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Array
Anon_

2021/07/21 08:54 編集

.
Lhankor_Mhy

2021/07/21 08:53

それは、書かなくても伝わるもんですか……?
Anon_

2021/07/21 08:55

あー、すみませんとんでもなく仕様の認識ミスをしておりました。
Anon_

2021/07/21 08:56

new PromiseとPromise.allがごっちゃになってしまっていました・・。
Anon_

2021/07/21 08:57

おっしゃるようにresolveを定義してないですね・・。
Lhankor_Mhy

2021/07/21 09:03

疑問が解決されたようでよかったです。 相談相手が欲しいなら、https://menta.work/ とか利用してみてはいかがですか?
Anon_

2021/07/26 04:25

ありがとうございました。
guest

0

何をしたいかよくわかりませんが
Promise.allの中でPromise.allをしても問題はありません
(多分しなくてもできることだとは思いますが・・・)

javascript

1for(let i=0;i<10;i++){ 2 setTimeout(()=>console.log(i),1000); 3}

※forの作業は普通に並行処理されます
これをasync/awaitしてしまうと同期処理になるので
Promise.allしても意味がないです

javascript

1(async()=>{ 2 for(let i=0;i<10;i++){ 3 await new Promise(resolve=>setTimeout(()=>{console.log(i),resolve()},1000)); 4 } 5})();

追記

javascript

1Promise.all(list.map(x=>fetch(x.url).then(res=>res.text()))).then(data=>{ 2 console.log(data); 3 Promise.all(list.map(x=>fetch(x.url+"?para=test").then(res=>res.text()))).then(data=>{ 4 console.log(data); 5 }); 6});

投稿2021/07/14 01:36

編集2021/07/14 05:13
yambejp

総合スコア116724

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Anon_

2021/07/14 02:22

ご回答ありがとうございます。 サンプルコードを追加いたしましたので、是非ご参照いただけますでしょうか。 forはpromise.allの中で使用しております。
yambejp

2021/07/14 04:58 編集

サンプルの意図がわかりません example.com sub2.example.com example.com?para=test sub2.example.com?para=test を並行してfetchしたいのですか? fetchがawaitである意味は何を想定していますか? fetchした戻り値はなにかにつかわないのですか?
yambejp

2021/07/14 05:13

example.com sub2.example.com の両方が戻ってきたら example.com?para=test sub2.example.com?para=test ということですかね? forでやることじゃないと思います
Anon_

2021/07/14 05:17 編集

うまく説明できずにすみません、 fetchがawaitである意味は、取得した値を得てから次の処理に進む想定です。 本来であれば、list変数の中に同じURLを3つづつ入れることで解決できますが、同じ値を配列に何度もいれるのは美しくないと思いますので、forで同じ処理を3回走らせようとした次第です。 ですので、 example.com example.com example.com sub2.example.com sub2.example.com sub2.example.com が同時に走る想定です。 パラメータをつけたURLへのfetchはawaitの意図を表したかっただけでしたが、分かりずらくなったようですみません。
yambejp

2021/07/14 05:31 編集

6個のfetchを同時並行でよいのですか? (前の処理を待たなくてOK?) パラメータの変更もなしにおなじurlに同時に飛ばす意味がないですが どういう想定でしょうか? 並行でよいなら Promise.all(Array(3).fill(null).map(x=>list.map(x=>fetch(x.url).then(res=>res.text()))).flat()).then(data=>console.log(data));
Anon_

2021/07/14 07:56 編集

"1度目のアクセスで必ず情報が取得できるというわけではない特殊な状況のため" と記載した通り、例え同じURLでも同じ結果とは限りません。 他の方の指摘の通り、処理中のbreakは非同期なのでできない事に気づいたため、別の良い方法を考えましたが、実際にはURLがもっと多いのでメモリリークを起こさないよう、listのループは同期的に行い、その中の3発同時リクエストは非同期で実行という形にしたいと思います。 その場合、下記のようなコードに変更してみたのですが、添削していただけるとありがたいです。 ※コメントだとコードが見づらかったので、本文の[修正コード]に移動しました。
yambejp

2021/07/14 08:03

前の処理に失敗したら再読み込みではなく、成功したら再読み込みの意味がわかりません。 ほぼ瞬時に成功したページを再度取りに行ってもキャッシュから読まれるだけでは?
Anon_

2021/07/14 08:07

成功したら再読み込みするとはいってないです。 キャッシュはクリアしているのでそこは気にしないでください。
yambejp

2021/07/14 08:07

(async()=>{ for(var i=0;i<3;i++){ await Promise.all(list.map(x=>fetch(x.url).then(res=>res.text()))); } })();
yambejp

2021/07/14 08:14 編集

> 成功したら再読み込みするとはいってないです。 Promise.allを待って次のPromise.allをするということは 成功したら再読み込みという意味ですが? 仕様をきちんと整理して質問したほうがよいでしょう 6個すべて並列でよいならすでに回答はつけてあります。 2個ずつ成功をまって読む方法も記載しました。 同じurlを叩いてキャッシュを読まない方法とはどういう 処置をしているのでしょうか?
Anon_

2021/07/14 08:14

async/awaitを忘れていましたね。 だいたいJSの書き方が理解できました。 とても参考になりました。 ありがとうございます。 BAを検討いたします。
Anon_

2021/07/14 08:36

Promise.allを待って次のPromise.allというのはどのコメントがそのように伝わってしまったのでしょうか。Promise.allの中でとは言いましたが、誤解させてしまい誠に申し訳ございません。 仮にリクエストしたページの要素が動的に変わるとして、 特定の値(仮に5)の場合にだけ次の処理に進むというイメージです。 単に同時にリクエストすればOKではなく、修正コードに記載した方法を見ていただければやりたいことは伝わるのではないかと思います。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問