JavaScript | 第14章「プロミスの使用」

javascrpit JavaScript
スポンサーリンク

Promiseチェーンの流れをリアルタイムで可視化するHTMLツール

See the Pen Promise Chain Visualization Tool by MONO365 -Color your days- (@monoqlo365) on CodePen.

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Promise チェーン 可視化ツール</title>
<style>
  :root{--bg:#0f1724;--card:#0b1220;--accent:#7c3aed;--ok:#10b981;--err:#ef4444;--muted:#94a3b8}
  html,body{height:100%;margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial}
  body{background:linear-gradient(180deg,#071029 0%, #071226 100%);color:#e6eef8;padding:20px}
  .app{max-width:1000px;margin:0 auto}
  header{display:flex;align-items:center;gap:16px}
  h1{font-size:20px;margin:0}
  .grid{display:grid;grid-template-columns:1fr 360px;gap:18px;margin-top:18px}
  .panel{background:var(--card);border-radius:12px;padding:14px;box-shadow:0 6px 24px rgba(2,6,23,.6)}
  .controls{display:flex;gap:8px;flex-wrap:wrap}
  button{background:transparent;border:1px solid rgba(255,255,255,.06);padding:8px 12px;border-radius:8px;color:inherit;cursor:pointer}
  button.primary{background:linear-gradient(90deg,var(--accent),#4f46e5);border:none}
  .stage{display:flex;gap:10px;align-items:center;justify-content:center;padding:16px}
  .node{width:150px;height:110px;border-radius:10px;background:linear-gradient(180deg,rgba(255,255,255,.02),transparent);border:1px solid rgba(255,255,255,.03);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative}
  .node .title{font-weight:600;margin-bottom:6px}
  .node .state{font-size:12px;color:var(--muted)}
  .node.pending{box-shadow:0 6px 18px rgba(4,6,12,.6);border-color:rgba(255,255,255,.04)}
  .node.fulfilled{outline:3px solid rgba(16,185,129,.12);box-shadow:0 10px 30px rgba(16,185,129,.06)}
  .node.rejected{outline:3px solid rgba(239,68,68,.1);box-shadow:0 10px 30px rgba(239,68,68,.06)}
  .arrow{width:60px;height:2px;background:linear-gradient(90deg,#ffffff33,#ffffff00);position:relative}
  .arrow::after{content:'';position:absolute;right:-6px;top:-6px;border:6px solid transparent;border-left-color:#ffffff33}
  .logs{height:260px;overflow:auto;background:linear-gradient(180deg,#071226,#07112a);padding:12px;border-radius:8px;border:1px solid rgba(255,255,255,.03)}
  .log{font-size:13px;padding:4px 6px;border-radius:6px;margin-bottom:6px}
  .log.info{color:#cfe7ff}
  .log.ok{color:var(--ok)}
  .log.err{color:var(--err)}
  .small{font-size:13px;color:var(--muted)}
  .row{display:flex;gap:8px;align-items:center}
  footer{margin-top:12px;color:var(--muted);font-size:13px}
  .controls .chip{background:#071a2d;padding:6px 8px;border-radius:8px;border:1px solid rgba(255,255,255,.03)}
  .node .dot{position:absolute;top:10px;right:10px;width:10px;height:10px;border-radius:50%;background:#334155}
  .node.fulfilled .dot{background:var(--ok)}
  .node.rejected .dot{background:var(--err)}
  .legend{display:flex;gap:8px;align-items:center}
  .legend .item{display:flex;gap:6px;align-items:center}
  .legend .sw{width:12px;height:12px;border-radius:3px}
  .sw.pending{background:#334155}
  .sw.fulfilled{background:var(--ok)}
  .sw.rejected{background:var(--err)}
  .speed{width:100%;}
  @media(max-width:880px){.grid{grid-template-columns:1fr;}.stage{flex-direction:column;gap:12px}}
</style>
</head>
<body>
  <div class="app">
    <header>
      <h1>Promise チェーン 可視化ツール</h1>
      <div class="small">ボタンでチェーンを実行し、各Promiseの状態変化をリアルタイム表示します。</div>
    </header>

    <div class="grid">
      <div class="panel">
        <div class="row controls" style="margin-bottom:12px;">
          <button class="primary" id="runChain">▶ チェーン実行</button>
          <button id="runAsync">▶ async/await 実行</button>
          <button id="runParallel">▶ Promise.all(並列)</button>
          <button id="reset">⟲ リセット</button>
          <div style="flex:1"></div>
          <div class="chip small">速度: <span id="speedLabel">1x</span></div>
        </div>

        <div class="stage" id="stage">
          <div class="node pending" id="n1">
            <div class="dot"></div>
            <div class="title">Promise 1 (wait 1s)</div>
            <div class="state">pending</div>
          </div>
          <div class="arrow" id="a1"></div>
          <div class="node pending" id="n2">
            <div class="dot"></div>
            <div class="title">Promise 2 (wait 2s)</div>
            <div class="state">pending</div>
          </div>
          <div class="arrow" id="a2"></div>
          <div class="node pending" id="n3">
            <div class="dot"></div>
            <div class="title">Promise 3 (wait 3s)</div>
            <div class="state">pending</div>
          </div>
        </div>

        <div style="margin-top:12px;display:flex;gap:8px;align-items:center;">
          <div class="legend">
            <div class="item"><div class="sw pending"></div><div class="small">pending</div></div>
            <div class="item"><div class="sw fulfilled"></div><div class="small">fulfilled</div></div>
            <div class="item"><div class="sw rejected"></div><div class="small">rejected</div></div>
          </div>
          <div style="flex:1"></div>
          <div class="small">※ then() は Promise を返すことでチェーンを待ちます</div>
        </div>

        <h3 style="margin-top:14px;margin-bottom:8px">ログ</h3>
        <div class="logs" id="logs"></div>
        <footer>
          <div class="small">このツールは学習用の簡易デモです。CodePen に貼って動かせます。</div>
        </footer>
      </div>

      <div class="panel">
        <h3>説明</h3>
        <p class="small">- <strong>チェーン実行</strong> は sequential な then チェーンを実演します。<br>- <strong>async/await 実行</strong> は同じ挙動を async / await で示します。<br>- <strong>Promise.all</strong> は並列実行の例(全員成功なら全部fulfilled)です。</p>

        <h3 style="margin-top:12px">速度コントロール</h3>
        <input id="speed" class="speed" type="range" min="0.25" max="3" step="0.25" value="1">
        <p class="small">スライダーでアニメーションとタイマーの速度を調節できます。</p>

        <h3 style="margin-top:12px">解説メモ</h3>
        <ul class="small">
          <li>各ノードは Promise の状態を色で表示します(pending / fulfilled / rejected)。</li>
          <li>then() が次の Promise を返すと、チェーンはその完了を待ちます。</li>
          <li>catch() が呼ばれると、その後のチェーンは rejected のまま進みます。</li>
        </ul>
      </div>
    </div>
  </div>

<script>
(() => {
  const n1 = document.getElementById('n1');
  const n2 = document.getElementById('n2');
  const n3 = document.getElementById('n3');
  const logs = document.getElementById('logs');
  const runChain = document.getElementById('runChain');
  const runAsync = document.getElementById('runAsync');
  const runParallel = document.getElementById('runParallel');
  const resetBtn = document.getElementById('reset');
  const speedEl = document.getElementById('speed');
  const speedLabel = document.getElementById('speedLabel');

  let speed = 1;
  function log(text, cls='info'){
    const el = document.createElement('div'); el.className = 'log '+cls; el.textContent = text; logs.prepend(el);
  }

  function setState(node, state, message){
    node.classList.remove('pending','fulfilled','rejected');
    node.classList.add(state);
    node.querySelector('.state').textContent = state;
    if(message) log(message, state==='fulfilled' ? 'ok' : (state==='rejected'?'err':'info'));
  }

  function wait(ms){
    return new Promise((resolve,reject)=>{
      const t = ms / speed;
      setTimeout(()=> resolve(`resolved after ${ms}ms`), t);
    });
  }

  function reset(){
    logs.innerHTML='';
    [n1,n2,n3].forEach(n => { n.classList.remove('fulfilled','rejected'); n.classList.add('pending'); n.querySelector('.state').textContent = 'pending'; });
    log('リセットしました');
  }

  speedEl.addEventListener('input', ()=>{ speed = parseFloat(speedEl.value); speedLabel.textContent = speed + 'x'; log('速度: ' + speed + 'x'); });

  resetBtn.addEventListener('click', reset);

  runChain.addEventListener('click', async ()=>{
    reset();
    log('thenチェーンを開始');

    // Promise 1
    setState(n1, 'pending');
    const p1 = new Promise((resolve,reject)=>{
      setState(n1,'pending');
      setTimeout(()=>{
        setState(n1,'fulfilled','Promise 1: fulfilled (1s)');
        resolve('p1');
      }, 1000 / speed);
    });

    p1
      .then((v)=>{
        log('then 1: p1の結果を受け取り、p2を返す');
        // return p2
        return new Promise((resolve)=>{
          setState(n2,'pending');
          setTimeout(()=>{
            setState(n2,'fulfilled','Promise 2: fulfilled (2s)');
            resolve('p2');
          }, 2000 / speed);
        });
      })
      .then((v)=>{
        log('then 2: p2の結果を受け取り、p3を返す');
        return new Promise((resolve)=>{
          setState(n3,'pending');
          setTimeout(()=>{
            setState(n3,'fulfilled','Promise 3: fulfilled (3s)');
            resolve('p3');
          }, 3000 / speed);
        });
      })
      .then((v)=>{
        log('最終 then に到達。チェーン完了');
      })
      .catch(e=>{
        log('チェーン中にエラー: '+e,'err');
      })
      .finally(()=>{
        log('チェーン終了(finally)');
      });
  });

  runAsync.addEventListener('click', async ()=>{
    reset();
    log('async/await を使った実行を開始');
    try{
      setState(n1,'pending');
      await new Promise(r=> setTimeout(()=>{ setState(n1,'fulfilled','Promise 1 fulfilled (1s)'); r('p1') }, 1000 / speed));

      setState(n2,'pending');
      await new Promise(r=> setTimeout(()=>{ setState(n2,'fulfilled','Promise 2 fulfilled (2s)'); r('p2') }, 2000 / speed));

      setState(n3,'pending');
      await new Promise(r=> setTimeout(()=>{ setState(n3,'fulfilled','Promise 3 fulfilled (3s)'); r('p3') }, 3000 / speed));

      log('async/await: 全ての await が完了');
    }catch(e){
      log('エラー: '+e,'err');
    }finally{
      log('async/await 実行終了(finally)');
    }
  });

  runParallel.addEventListener('click', ()=>{
    reset();
    log('Promise.all(並列実行)を開始');
    setState(n1,'pending'); setState(n2,'pending'); setState(n3,'pending');
    const p1 = new Promise(r=> setTimeout(()=>{ setState(n1,'fulfilled','p1 fulfilled (1s)'); r('p1'); }, 1000 / speed));
    const p2 = new Promise(r=> setTimeout(()=>{ setState(n2,'fulfilled','p2 fulfilled (2s)'); r('p2'); }, 2000 / speed));
    const p3 = new Promise(r=> setTimeout(()=>{ setState(n3,'fulfilled','p3 fulfilled (3s)'); r('p3'); }, 3000 / speed));

    Promise.all([p1,p2,p3]).then(results=>{
      log('Promise.all: 全て成功 -> ' + results.join(', '));
    }).catch(e=>{
      log('Promise.all: どれか失敗 -> '+e,'err');
    }).finally(()=>{
      log('Promise.all 終了');
    });
  });

  // initial
  reset();
})();
</script>
</body>
</html>
JavaScript

開いて ▶ チェーン実行 / async/await 実行 / Promise.all(並列) のいずれかを押すと、各ノード(Promise)の状態がリアルタイムで変化してログに記録されます。速度スライダーでアニメーション速さも調整できます。

タイトルとURLをコピーしました