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)の状態がリアルタイムで変化してログに記録されます。速度スライダーでアニメーション速さも調整できます。
