画像のロードが成功するか失敗するかのテスト対象とするバージョン番号の最小値(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
上記を実際にブラウザで動かすと画面上に
と表示されるかと思います。
関数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 で抜けるようにしています。