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

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

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

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

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

5回答

996閲覧

ループ処理でURLを正誤判定したい(2)

mii2

総合スコア12

JavaScript

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

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

1クリップ

投稿2022/08/31 07:16

編集2022/08/31 08:48

前提

こちらのつづきです。

htmlファイル内で他サイトからimgを読み込んでいます。

<img src="https://abcde.com/abcde/*/assets/img01.jpg"> <img src="https://abcde.com/abcde/*/assets/img02.jpg"> …

ただ、システムの都合上、imgが置いてあるフォルダ名の*部分が「v1」「v2」・・・と
数字が増え、URLが変わってしまいます。

こちらでは正解のURLが分からないため、jsで画像が読み込めるよう処理できないかと検討しています。

ループ処理でv1,2・・・とURLを変えていき
"img.onload"で正しいURLかどうかを判定できるか試したのですが、上手くいきません。

img01については
<img src="https://abcde.com/abcde/v1/assets/img01.jpg">→404エラー
<img src="https://abcde.com/abcde/v2/assets/img01.jpg">→404エラー
<img src="https://abcde.com/abcde/v3/assets/img01.jpg">→OK
と、画像が表示されるまで順番にあたっていき、
サイトにきちんと画像が表示されるようにしたいのですが、現状「v1」で止まり、404エラーのままです。

参考にしたサイトはこちらです。

何か良い策はないでしょうか?

試したもの

<img src="img01url"> <img src="img02url"> <script> const img = new Image(); let i = 1; img.onerror = function() { img01url = img.src; i=i++ } img.onload = function() { const img01url = 'https://abcde.com/abcde/v'+'i'+'/img01.jpg'; const img02url = 'https://abcde.com/abcde/v'+'i'+'/img02.jpg'; } </script>

試したもの2

<img src="img01url"> <img src="img02url"> <script> const img = new Image(); for (let i = 1; ???; i++){ img.onload = function() { const img01url = 'https://abcde.com/abcde/v'+'i'+'/img01.jpg'; const img02url = 'https://abcde.com/abcde/v'+'i'+'/img02.jpg'; } } </script>

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

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

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

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

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

maisumakun

2022/08/31 07:28

> 上手くいきません。 どのような動作をさせる予定のものが、「実際にはどうなっている」状況でしょうか。
mii2

2022/08/31 07:37

img01については~ で追記いたしました。
guest

回答5

0

画像のロードが成功するか失敗するかのテスト対象とするバージョン番号の最小値(low)と最大値(high)を与えて、その範囲内で画像のロードが成功する番号を見つけて返す関数

getAvailableVersion(low, high)

を作ることを考えます。この場合、forを使ったループではなく、Promise.any() を使うとよいのではと思いましたので、これを試すコードを以下に挙げておきます。

まず以下のようなHTMLを作っておきます。

html

1<!DOCTYPE html> 2<html lang="ja"> 3<head> 4 <meta charset="UTF-8"> 5 <title>jf1nb4ipts8yv3</title> 6 <script src="main.js"></script> 7</head> 8<body> 9有効な画像バージョン: <span id="result"></span> 10</body> 11</html>

上記のHTMLで使っているmain.js として、以下を作成します。

javascript

1document.addEventListener('DOMContentLoaded', async function() { 2 let availableVersion = '不明'; 3 4 try { 5 availableVersion = await getAvailableVersion(48, 90); 6 } catch (e) { 7 console.error(e); 8 } 9 10 document.getElementById('result').textContent = `${availableVersion}`; 11}); 12 13 14async function getAvailableVersion(low, high) { 15 16 const baseUrl = 'https://dummyimage.com/100{VER}100/000/fff.jpg'; 17 18 const imageLoadingTester = (imageVersion) => ( 19 new Promise((resolve, reject) => { 20 const src = baseUrl.replace('{VER}', String.fromCharCode(imageVersion)); 21 const img = document.createElement('img'); 22 img.onload = function() { 23 resolve(imageVersion); 24 } 25 img.onerror = function() { 26 reject(); 27 } 28 img.src = src; 29 }) 30 ); 31 32 const testers = [...Array(high - low + 1)].map((_, i) => imageLoadingTester(low + i)); 33 34 return await Promise.any(testers); 35} 36

上記を実際にブラウザで動かすと画面上に

有効な画像バージョン: 88

と表示されるかと思います。

関数getAvailableVersion(low, high) のやっていることは、ダミーの画像を生成してくれるサイト dummyimage.com を利用して、以下のパターンのURL

https://dummyimage.com/100{VER}100/000/fff.jpg

{VER} の部分を、low 以上 high 以下の整数値のASCII コードに対応する一文字に置き換えたURLを作り、それらのURLをsrc属性とするimg要素を作ってloadできる番号が見つかればその値でresolveし、見つからなければrejectされるPromiseを返します。

上記のコードでは

javascript

1availableVersion = await getAvailableVersion(48, 90);

としており、48以上90以下の中では唯一、ASCIIコード 88に対応する X で置き換えた

https://dummyimage.com/100X100/000/fff.jpg

がloadに成功するので、imageVersion が 88 のときのPromiseだけがresolveされて他はrejectされます。試す番号の上限を 80 にして

javascript

1availableVersion = await getAvailableVersion(48, 80);

とすると、すべてのPromiseが失敗するので Promise.any() も失敗して、その結果

有効な画像バージョン: 不明

と表示され、console には catch されたエラーの情報

AggregateError: All promises were rejected

が表示されます。

(※ 上記のコードを試す際に、一度に大量のリクエストをdummyimage.comに送らないようご配慮願います)

追記

上記のコードでは、Promise.any() を使うことでlow以上high以下のバージョンのURLで画像をロードできるかどうか、複数のリクエストを同時にサーバーに投げますが、そうではなく最小のバージョン番号のURLから順にひとつずつロードできるかを試して、最初にロードに成功したバージョン番号が見つかったところでテストを終了するというコードにしたいのであれば、方法として async ジェネレータと for await ... of の組み合わせが考えられます。

その場合、先のコードの main.js を以下のようにします。

javascript

1document.addEventListener('DOMContentLoaded', async function() { 2 let availableVersion = 0; 3 4 for await (const { available, imageVersion } of versionTester(80, 90)) { 5 if (available) { 6 availableVersion = imageVersion; 7 break; 8 } 9 } 10 11 document.getElementById('result').textContent = `${availableVersion || '不明'}`; 12}); 13 14 15async function* versionTester(low, high) { 16 const baseUrl = 'https://dummyimage.com/100{VER}100/000/fff.jpg'; 17 let imageVersion = low; 18 19 while (imageVersion <= high) { 20 const result = await new Promise(resolve => { 21 const src = baseUrl.replace('{VER}', String.fromCharCode(imageVersion)); 22 const img = document.createElement('img'); 23 img.onload = function() { 24 resolve({ imageVersion, available: true }); 25 } 26 img.onerror = function() { 27 resolve({ imageVersion, available: false }); 28 } 29 img.src = src; 30 }); 31 32 yield result; 33 imageVersion ++; 34 } 35}

このコードでは versionTester(low, high) というasyncジェネレータ関数を作り、これの返すasyncジェネレータを for await ... of構文で各非同期処理の結果を取得し、有効なバージョン番号がみつかった時点で break で抜けるようにしています。

投稿2022/08/31 19:11

編集2022/08/31 20:27
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

mii2

2022/09/01 01:12

promiseですね、こちらがいいのかな、とも思っていたのですが、なかなか難しく手が出せずにいました。 解説いただき、ありがとうございます。 たしかに、1度に大量のリクエストを投げるのも気になる点ではあるので、for awaitの方がいいかもしれません。 丁寧に解説いただきありがとうございました。 大変勉強になりました
guest

0

ベストアンサー

試行錯誤されているようなので、サンプルを提示します。
あくまで私ならこんな感じで実装するよといったものです。
処理内容についてはソース内のコメントを参照してください。

html

1<!-- data-src属性に画像のURLを指定 --> 2<!-- 可変部分は"{}"で表記 --> 3<img data-src="https://example.com/abcde/v{}/img01.jpg"> 4<img data-src="https://example.com/abcde/v{}/img02.jpg"> 5 6<script> 7const loadImg = () => { 8 // 置換対象文字列 9 const REPLACE_STR = '{}'; 10 // 初期取得バージョン 11 const INIT_VER = 1; 12 // 最大取得バージョン 13 const MAX_VER = 10; 14 15 // data-src属性が指定されたimgタグを検索 16 // 該当のタグそれぞれに処理を適用する 17 document.querySelectorAll('img[data-src]').forEach(imgNode => { 18 19 // data-src属性の値を取得 20 const srcTempl = imgNode.dataset.src; 21 22 const updateSrc = ver => { 23 // img要素を生成 24 const img = new Image(); 25 26 // srcTemplの置換対象文字列を数字(ver)に置換してimg要素のsrcに設定 27 const src = srcTempl.replace(REPLACE_STR, ver); 28 img.src = src; 29 30 // 画像読み成功時のイベントハンドラ 31 // imgタグのsrcを読み込みが成功した画像のURLに変更する 32 img.onload = () => imgNode.src = img.src; 33 34 // 画像読み込み失敗時のイベントハンドラ 35 // バージョンがMAX_VER未満であれば数字をカウントアップして再帰を行う 36 img.onerror = () => ver < MAX_VER && updateSrc(ver + 1); 37 }; 38 updateSrc(INIT_VER); 39 }); 40}; 41loadImg(); 42</script>

試したものについて

イベントハンドラは即時で実行されるものではなく、条件を満たした場合に呼び出されるものです。
onloadであれば読み込みが成功したときにこの処理をやってね~と予約するイメージです。

そのため、for文内でイベントハンドラを設定しても、次のループ前に指定された処理が発火している保証はありません。
(画像の読み込みが成功したかわからないうちに次の番号でリクエストしてしまう)

特に、onloadでイベントを設定した場合は次のループ時に予約した処理を上書きしてしまい、仮に読み込みが成功していたとしても処理が行われなくなります。
(上書きされないように併存させるにはaddEventListenerを使用するのが望ましいです)

今回、エラーになった時に再取得処理を行いたいので、サンプルソースではエラーのイベントハンドラ内で関数を再呼出してループを実現しています。

追記

例示の際のドメインについて

試したものに記載のドメインabcde.comは実在するものです。
もし、あなたが管理されているドメインでない場合はexample.comなど例示用として予約されているドメインを用いるのが望ましいです。
(回答ソースのドメインも修正しました。)

投稿2022/08/31 15:31

編集2022/09/01 00:30
t-n

総合スコア136

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

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

mii2

2022/09/01 01:08

onerror内でループができていますね とても分かりやすいです。こちらを参考にさせていただいたところ、無事動きました。 ドメインについても、ご指摘ありがとうございます。今後気を付けます。 ご丁寧に解説してくださり、ありがとうございました!
guest

0

試したもの2について

iを文字列として挿入しているのが気になります。
またループの中にonloadがあると実行される関数が上書きされて最後のループにおけるiのみが反映されるかもです。

投稿2022/08/31 10:22

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

mii2

2022/08/31 10:29

回答ありがとうございます。 URLの一部なので、文字列で入れてしまいました… onloadはfor文の外に出すということでしょうか?
退会済みユーザー

退会済みユーザー

2022/08/31 10:43 編集

onloadの中にfor文を入れるといいのではないでしょうか。 またfor文の中でimg01urlなどを定義しているので、for文の中でimg01urlなどを使う処理をすればいいです。多分。 ↓こんな感じかな?みたいな const img = new Image(); img.onload = function() { for (let i = 1; /*iの条件。仮→*/i < 3; i++) { const img01url = 'https://abcde.com/abcde/v' + i + '/img01.jpg'; const img02url = 'https://abcde.com/abcde/v' + i + '/img02.jpg'; //処理する文 仮↓ console.log(img01url); } };
mii2

2022/09/01 01:05

ご回答ありがとうございます。 for文の中に入れた方が良いのですね、勉強になりました!
guest

0

試したもの

そもそも、コードがループする要素がないです。

投稿2022/08/31 08:26

maisumakun

総合スコア145183

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

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

mii2

2022/08/31 08:33 編集

不勉強で申し訳ありません。 数が増えていくだけなので、ループではないですね。 どのように設定したら良いでしょうか?
maisumakun

2022/08/31 08:35

設定というより、まずはループするようなコードを書いてください。
mii2

2022/08/31 08:46

for構文で作成しようとしましたが、書き方が分からない状態です。 追記しました。
guest

0

呼び出し用のimg要素を作ってsrcを指定しながらloadをチェックすればいいのでは?

投稿2022/08/31 07:26

yambejp

総合スコア114829

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

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

mii2

2022/08/31 07:40

ご回答ありがとうございます。 「呼び出し用のimg要素」というのは、htmlのimgタグで指定しているものとは異なりますか? 不勉強で申し訳ないのですが、参考の記述などあれば、詳しく教えていただけますと幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問