「Image.onloadはやめてImage.decodeを使おうね」はループの中以外の話 #JavaScript - Qiita

画像を複数枚jsで読み込む実装を行なっていたところ、
generosenninさんから「onloadよりimage.decodeを使った方がいいよ。」とアドバイスいただきました。

ただ、実際に実装するとなぜかうまくいかなかったので調査した結果をここに記します

for文で回しながら画像を取得する処理において、image.decodeを利用すると、下記のエラーが発生する。
これは特定の画像やタイミングで起きるものではなく、不定期のタイミングで発生する(ただ、必ずに近いレベルで発生した)

EncodingError: The source image cannot be decoded.

imagesフォルダに連番で1.png~30.pngを適当に配置する

HTMLを適当に作る

index.html


 lang="en">

   charset="UTF-8">
   name="viewport" content="width=device-width, initial-scale=1.0">
  </span>Document<span class="nt"/>
<span class="nt"/>
<span class="nt"/>
  
  <span class="nt"><script><![CDATA[<span class="na">src=]]></script></span><span class="s">"main.js"</span><span class="nt">></span>
<span class="nt"/>
<span class="nt"/>
</span></span></span></span></code></pre>
</div>
</div>
<p data-sourcepos="35:1-35:10">Javascript</p>
<div class="code-frame" data-lang="javascript" data-sourcepos="36:1-49:3">
<p><span class="bold">main.js</span></p>
<div class="highlight">
<pre><code><span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">index</span> <span class="o"> <span class="mi">30</span><span class="p">;</span> <span class="nx">index</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Image</span><span class="p">();</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s2">`./images/</span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">.jpeg`</span><span class="p">;</span>
  <span class="nx">img</span>
    <span class="p">.</span><span class="nf">decode</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">encodingError</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`画像 </span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2"> のロード中にエラーが発生しました:`</span><span class="p">,</span> <span class="nx">encodingError</span><span class="p">);</span>
    <span class="p">});</span>
<span class="p">}</span>
</span></code></pre>
</div>
</div>
<p data-sourcepos="51:1-51:79">これでindex.htmlを開くと該当のエラーの発生を確認できる。</p>
<p data-sourcepos="53:1-53:69">同じことをimage.onloadでやるとエラーは発生しない。</p>
<div class="code-frame" data-lang="javascript" data-sourcepos="55:1-68:3">
<p><span class="bold">main.js</span></p>
<div class="highlight">
<pre><code><span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">index</span> <span class="o"> <span class="mi">30</span><span class="p">;</span> <span class="nx">index</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Image</span><span class="p">();</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s2">`./images/</span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">.jpeg`</span><span class="p">;</span>
  <span class="nx">img</span>
    <span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
    <span class="p">}</span>

  <span class="nx">img</span><span class="p">.</span><span class="nx">onerror</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`画像 </span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2"> のロード中にエラーが発生しました:`</span><span class="p">,</span> <span class="nx">encodingError</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</span></code></pre>
</div>
</div>
<p data-sourcepos="72:1-72:69">ドキュメントをちゃんと読めば書いてありますが、</p>
<p data-sourcepos="74:1-74:69"><iframe id="qiita-embed-content__379e4082ff61867316e0db67bf4d7ab8" src="https://qiita.com/embed-contents/link-card#qiita-embed-content__379e4082ff61867316e0db67bf4d7ab8" data-content="https%3A%2F%2Fdeveloper.mozilla.org%2Fja%2Fdocs%2FWeb%2FAPI%2FHTMLImageElement%2Fdecode" frameborder="0" scrolling="no" loading="lazy" style="width:100%;" height="29"><br />
</iframe>
</p>
<p data-sourcepos="76:1-76:163"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F303906%2F8e5b9a69-8e05-453a-8d01-7ae067bab873.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=4249a3691995e4c0f015d9544da9dda1" target="_blank" rel="nofollow noopener"><img decoding="async" src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F303906%2F8e5b9a69-8e05-453a-8d01-7ae067bab873.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=4249a3691995e4c0f015d9544da9dda1" alt="スクリーンショット 2025-04-19 13.24.16.png" srcset="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F303906%2F8e5b9a69-8e05-453a-8d01-7ae067bab873.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=b4dcf5d166150f3713a228db2fa6ec99 1x" data-canonical-src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303906/8e5b9a69-8e05-453a-8d01-7ae067bab873.png" loading="lazy"/></a></p>
<p data-sourcepos="78:1-79:261">DOMに追加しても”安全になったとき”に解決されるプロミスな訳です。<br />for文で回している以上、decodeが安全性を確認する前に次のdecodeが実行されてしまい、安全性を解決できずにデコードできない画像が生まれてしまうことが原因でこの問題が起きているようです。</p>
<p data-sourcepos="81:1-81:75">※あくまで私の意見なので、違ったら教えてください。</p>
<p data-sourcepos="84:1-84:201">画像単体の読み込みや、逐次的に画像を読み込む場合(ボタンを押したらロードされる仕組みなど)であれば、onloadよりも優先して利用すべきです。</p>
<p data-sourcepos="86:1-86:84">例えば下記のような仕組みで利用する分には問題ありません。</p>
<div class="code-frame" data-lang="javascript" data-sourcepos="88:1-131:3">
<p><span class="bold">main.js</span></p>
<div class="highlight">
<pre><code><span class="kd">let</span> <span class="nx">currentImageIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">totalImages</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span>

<span class="c1">// 次の画像を読み込む関数</span>
<span class="kd">function</span> <span class="nf">loadNextImage</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">currentImageIndex</span> <span class="o">>=</span> <span class="nx">totalImages</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">すべての画像がロードされました</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="kd">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Image</span><span class="p">();</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s2">`./images/</span><span class="p">${</span><span class="nx">currentImageIndex</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">.jpeg`</span><span class="p">;</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">200px</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">auto</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">img</span><span class="p">.</span><span class="nx">alt</span> <span class="o">=</span> <span class="s2">`画像 </span><span class="p">${</span><span class="nx">currentImageIndex</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
  
  <span class="c1">// 画像をデコードして表示</span>
  <span class="nx">img</span><span class="p">.</span><span class="nf">decode</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`画像 </span><span class="p">${</span><span class="nx">currentImageIndex</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2"> をロードしました`</span><span class="p">);</span>
      <span class="nx">currentImageIndex</span><span class="o">++</span><span class="p">;</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">encodingError</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">`画像 </span><span class="p">${</span><span class="nx">currentImageIndex</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2"> のロード中にエラーが発生しました:`</span><span class="p">,</span> <span class="nx">encodingError</span><span class="p">);</span>
      <span class="nx">currentImageIndex</span><span class="o">++</span><span class="p">;</span> <span class="c1">// エラーが発生した場合も次の画像に進む</span>
    <span class="p">});</span>
<span class="p">}</span>

<span class="c1">// ページ読み込み時に最初の画像をロード</span>
<span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
  <span class="c1">// ボタンを作成して画面に追加</span>
  <span class="kd">const</span> <span class="nx">loadButton</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">loadButton</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">次の画像を読み込む</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">loadButton</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">block</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">loadButton</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">margin</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">10px 0</span><span class="dl">'</span><span class="p">;</span>
  <span class="nx">loadButton</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="nx">loadNextImage</span><span class="p">);</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">loadButton</span><span class="p">);</span>
  
  <span class="c1">// 最初の画像を自動的にロード</span>
  <span class="nf">loadNextImage</span><span class="p">();</span>
<span class="p">});</span>
</code></pre>
</div>
</div>
<p data-sourcepos="134:1-135:141">やりたいことに応じて、decodeとonloadを適切に使い分けるのが良さそう。<br />基本的にはdecodeを使いつつ、大量の画像を一気に呼ぶ分にはonloadでやる方針で実装できると良いですね〜</p>
</div>
<p><script>!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '305156090176370');
fbq('trackSingle', '305156090176370', 'PageView');</script><br />
<br /><a href="https://h.accesstrade.net/sp/cc?rk=0100oyf000odbd" rel="nofollow" referrerpolicy="no-referrer-when-downgrade"><img decoding="async" src="https://h.accesstrade.net/sp/rr?rk=0100oyf000odbd" alt="フラッグシティパートナーズ海外不動産投資セミナー" border="0" /></a>
<a href="https://h.accesstrade.net/sp/cc?rk=01004iw600odbd" rel="nofollow" referrerpolicy="no-referrer-when-downgrade"><img decoding="async" src="https://h.accesstrade.net/sp/rr?rk=01004iw600odbd" alt="【DMM FX】入金" border="0" /></a><br />
<br /><a href="https://qiita.com/Yohei_Suda/items/1a7dccb00b2b87661b9e?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items">Source link </a></p>
<div class='amazon-auto-links'><div class="amazon-products-container-list amazon-unit-29349 unit-type-category" style="">
        <div class="amazon-product-container">
        <div class="amazon-auto-links-product">
    <div class="amazon-auto-links-product-image">
        <div class='amazon-product-thumbnail-container' data-href='https://www.amazon.co.jp/KIOXIA-%E3%82%AD%E3%82%AA%E3%82%AF%E3%82%B7%E3%82%A2-microSDHC%E3%82%AB%E3%83%BC%E3%83%89-Amazon-co-jp%E3%83%A2%E3%83%87%E3%83%AB-KLMEA032G/dp/B08PTNWQ6P/ref=zg_bs_g_computers_d_sccl_23/356-3178928-8054451?psc=1&tag=inmobi06-22' data-large-src='https://images-fe.ssl-images-amazon.com/images/I/712q0ScCuWL._AC_UL500_SR500,500_.jpg'><div class="amazon-product-thumbnail" style="max-width:160px;max-height:160px;width:160px">
    <a href="https://www.amazon.co.jp/KIOXIA-%E3%82%AD%E3%82%AA%E3%82%AF%E3%82%B7%E3%82%A2-microSDHC%E3%82%AB%E3%83%BC%E3%83%89-Amazon-co-jp%E3%83%A2%E3%83%87%E3%83%AB-KLMEA032G/dp/B08PTNWQ6P/ref=zg_bs_g_computers_d_sccl_23/356-3178928-8054451?psc=1&tag=inmobi06-22" title="KIOXIA(キオクシア) 旧東芝メモリ microSD 32GB UHS-I Class10 (最大読出速度100MB/s) Nintendo Switch動作確認済 国内サポート正規品 メーカー保証5年 KLMEA032G: " rel="nofollow noopener" target="_blank">
        <img decoding="async" src="https://images-fe.ssl-images-amazon.com/images/I/712q0ScCuWL._AC_UL160_SR160,160_.jpg" alt="" style="max-height:160px" />
    </a>
</div></div>
    </div>
    <div class="amazon-auto-links-product-body">
        <h5 class="amazon-product-title">
<a href="https://www.amazon.co.jp/KIOXIA-%E3%82%AD%E3%82%AA%E3%82%AF%E3%82%B7%E3%82%A2-microSDHC%E3%82%AB%E3%83%BC%E3%83%89-Amazon-co-jp%E3%83%A2%E3%83%87%E3%83%AB-KLMEA032G/dp/B08PTNWQ6P/ref=zg_bs_g_computers_d_sccl_23/356-3178928-8054451?psc=1&tag=inmobi06-22" title="KIOXIA(キオクシア) 旧東芝メモリ microSD 32GB UHS-I Class10 (最大読出速度100MB/s) Nintendo Switch動作確認済 国内サポート正規品 メーカー保証5年 KLMEA032G: " rel="nofollow noopener" target="_blank">KIOXIA(キオクシア) 旧東芝メモリ microSD 32GB UHS-I Class10 (最大読出速度100MB/s) Nintendo Switch動作確認済 国内サポート正規品 メーカー保証5年 KLMEA032G</a>
</h5>
        <div class='amazon-customer-rating-stars'><div class='crIFrameNumCustReviews' data-rating='44' data-review-count='16759' data-review-url='https://www.amazon.co.jp/product-reviews/B08PTNWQ6P?tag=inmobi06-22'><span class='crAvgStars'><span class='review-stars'><a href='https://www.amazon.co.jp/product-reviews/B08PTNWQ6P?tag=inmobi06-22' target='_blank' rel='nofollow noopener'><svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 160 32' enable-background='new 0 0 160 32'><title>5星中4.4(16759)
¥640 (2025年4月25日 13:07 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)