pin_drop当前位置:知识文库 ❯ 图文
后端通信:Promise与异步编程 - 从入门到实践详解
教程简介
JavaScript是单线程语言,异步编程是其核心特性之一。从回调函数到Promise,再到async/await,JavaScript的异步编程模型经历了重大演进。Promise是现代JavaScript异步编程的基石,它解决了回调地狱问题,提供了更优雅的异步流程控制方式。理解Promise的工作原理、链式调用、错误处理和组合方法,是掌握前后端通信和异步编程的关键。
本教程将全面讲解回调地狱问题、Promise基础、then/catch/finally、Promise链、Promise组合方法、错误处理和异步流程控制。
核心概念
1. 回调地狱
传统回调方式处理异步操作:
代码示例
// 回调地狱 - 金字塔形的嵌套结构
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
getProducts(detail.productId, function(products) {
console.log(products);
}, function(error) {
console.error(error);
});
}, function(error) {
console.error(error);
});
}, function(error) {
console.error(error);
});
}, function(error) {
console.error(error);
});回调地狱的问题:
代码嵌套层级深,难以阅读
错误处理分散,容易遗漏
无法正常使用try/catch
难以维护和扩展
2. Promise基础
Promise是一个代表异步操作最终完成或失败的对象。它有三种状态:
代码示例
// 创建Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject(new Error('操作失败'));
}
}, 1000);
});3. then/catch/finally
代码示例
promise
.then(value => {
// 成功处理
console.log(value);
return '处理后的值'; // 返回值会传给下一个then
})
.then(value => {
// 链式调用
console.log(value);
})
.catch(error => {
// 错误处理
console.error(error);
})
.finally(() => {
// 无论成功失败都执行
console.log('操作结束');
});then的返回值规则:
4. Promise链
Promise链解决了回调地狱,实现扁平化的异步流程:
代码示例
fetch('/api/user/1')
.then(res => res.json())
.then(user => fetch(`/api/orders?userId=${user.id}`))
.then(res => res.json())
.then(orders => fetch(`/api/orders/${orders[0].id}`))
.then(res => res.json())
.then(detail => console.log(detail))
.catch(error => console.error('链中任一环节出错:', error));5. Promise组合方法
Promise.all - 全部成功
代码示例
// 所有Promise都成功时返回结果数组
// 任一失败则整体失败
const results = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/orders').then(r => r.json()),
fetch('/api/products').then(r => r.json())
]);
// results[0] = 用户数据, results[1] = 订单数据, results[2] = 产品数据Promise.allSettled - 全部完成
代码示例
// 等待所有Promise完成(无论成功失败)
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/orders').then(r => r.json()),
fetch('/api/products').then(r => r.json())
]);
// results = [
// { status: 'fulfilled', value: {...} },
// { status: 'rejected', reason: Error }
// ]Promise.race - 最快完成
代码示例
// 返回最先完成的Promise结果
const fastest = await Promise.race([
fetch('/cdn1/data.json').then(r => r.json()),
fetch('/cdn2/data.json').then(r => r.json())
]);Promise.any - 最快成功
代码示例
// 返回最先成功的Promise结果
// 全部失败才失败
try {
const fastest = await Promise.any([
fetch('/cdn1/data.json').then(r => r.json()),
fetch('/cdn2/data.json').then(r => r.json())
]);
} catch (error) {
// AggregateError: 所有Promise都失败
console.error(error.errors);
}组合方法对比:
6. Promise.resolve/reject
代码示例
// Promise.resolve - 快速创建已完成的Promise
const p1 = Promise.resolve(42);
const p2 = Promise.resolve({ name: '张三' });
// Promise.reject - 快速创建已拒绝的Promise
const p3 = Promise.reject(new Error('出错了'));7. 错误处理
代码示例
// 方式1:catch方法
promise.catch(error => console.error(error));
// 方式2:then的第二个参数
promise.then(null, error => console.error(error));
// 方式3:try/catch + async/await
try {
const result = await promise;
} catch (error) {
console.error(error);
}
// 错误冒泡:链中未处理的错误会向后传递
fetch('/api/data')
.then(res => res.json())
// 这里没有catch,错误会传递
.then(data => processData(data))
.catch(error => {
// 捕获链中任何环节的错误
console.error(error);
});8. 异步流程控制
代码示例
// 串行执行 - 依次执行
async function serial(tasks) {
const results = [];
for (const task of tasks) {
results.push(await task());
}
return results;
}
// 并行执行 - 同时执行
async function parallel(tasks) {
return Promise.all(tasks.map(task => task()));
}
// 限制并发数
async function limitConcurrency(tasks, limit) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = task().then(result => {
executing.delete(p);
return result;
});
executing.add(p);
results.push(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}代码示例
示例1:Promise状态与链式调用演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise状态与链式调用演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.state-visual { display: flex; align-items: center; justify-content: center; gap: 20px; padding: 30px; margin-bottom: 20px; }
.state-circle { width: 100px; height: 100px; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; border: 3px solid #dadce0; color: #999; transition: all 0.5s; }
.state-circle.active-pending { border-color: #ff9800; color: #ff9800; background: #fff3e0; }
.state-circle.active-fulfilled { border-color: #34a853; color: #34a853; background: #e8f5e9; }
.state-circle.active-rejected { border-color: #ea4335; color: #ea4335; background: #ffebee; }
.arrow { font-size: 24px; color: #dadce0; }
.demo-btns { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.btn-blue { background: #1a73e8; color: #fff; }
.btn-green { background: #34a853; color: #fff; }
.btn-red { background: #ea4335; color: #fff; }
.btn-orange { background: #ff9800; color: #fff; }
.chain-visual { padding: 16px; background: #f8f9fa; border-radius: 8px; margin-top: 16px; }
.chain-step { display: flex; align-items: center; gap: 10px; padding: 10px; margin-bottom: 8px; border-radius: 6px; font-family: 'Consolas', monospace; font-size: 13px; }
.chain-step.pending { background: #fff3e0; color: #ef6c00; }
.chain-step.fulfilled { background: #e8f5e9; color: #2e7d32; }
.chain-step.rejected { background: #ffebee; color: #c62828; }
.chain-arrow { text-align: center; color: #dadce0; font-size: 18px; margin: 4px 0; }
.log-area { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.8; max-height: 300px; overflow-y: auto; white-space: pre-wrap; }
</style>
</head>
<body>
<div class="container">
<h1>Promise状态与链式调用</h1>
<div class="card">
<h2>Promise三种状态</h2>
<div class="state-visual">
<div class="state-circle active-pending" id="statePending"><span>pending</span><span style="font-size:11px">待定</span></div>
<div class="arrow">→</div>
<div class="state-circle" id="stateFulfilled"><span>fulfilled</span><span style="font-size:11px">已完成</span></div>
<div class="state-circle" id="stateRejected"><span>rejected</span><span style="font-size:11px">已拒绝</span></div>
</div>
<div class="demo-btns">
<button class="btn-green" onclick="demoResolve()">Resolve</button>
<button class="btn-red" onclick="demoReject()">Reject</button>
<button class="btn-orange" onclick="demoReset()">重置</button>
</div>
</div>
<div class="card">
<h2>Promise链式调用</h2>
<div class="demo-btns">
<button class="btn-blue" onclick="demoChain()">执行链式调用</button>
<button class="btn-red" onclick="demoChainError()">链中错误处理</button>
</div>
<div class="chain-visual" id="chainVisual"></div>
<div class="log-area" id="chainLog" style="margin-top:12px;">等待执行...</div>
</div>
</div>
<script>
function demoResolve() {
demoReset();
setTimeout(() => {
document.getElementById('statePending').className = 'state-circle';
document.getElementById('stateFulfilled').className = 'state-circle active-fulfilled';
}, 1000);
}
function demoReject() {
demoReset();
setTimeout(() => {
document.getElementById('statePending').className = 'state-circle';
document.getElementById('stateRejected').className = 'state-circle active-rejected';
}, 1000);
}
function demoReset() {
document.getElementById('statePending').className = 'state-circle active-pending';
document.getElementById('stateFulfilled').className = 'state-circle';
document.getElementById('stateRejected').className = 'state-circle';
}
function demoChain() {
const log = document.getElementById('chainLog');
const visual = document.getElementById('chainVisual');
log.textContent = '';
visual.innerHTML = '<div class="chain-step pending">Step 1: fetch用户数据... (pending)</div>';
new Promise(resolve => {
setTimeout(() => {
log.textContent += 'Step 1: 获取用户数据成功\n';
visual.innerHTML += '<div class="chain-arrow">↓ .then()</div><div class="chain-step pending">Step 2: 获取订单数据... (pending)</div>';
visual.querySelector('.chain-step:first-child').className = 'chain-step fulfilled';
resolve({ id: 1, name: '张三' });
}, 1000);
}).then(user => {
return new Promise(resolve => {
setTimeout(() => {
log.textContent += `Step 2: 获取用户 ${user.name} 的订单成功\n`;
visual.innerHTML += '<div class="chain-arrow">↓ .then()</div><div class="chain-step pending">Step 3: 获取订单详情... (pending)</div>';
visual.querySelectorAll('.chain-step')[1].className = 'chain-step fulfilled';
resolve([{ id: 101, product: '商品A' }]);
}, 1000);
});
}).then(orders => {
return new Promise(resolve => {
setTimeout(() => {
log.textContent += `Step 3: 获取订单详情成功\n`;
visual.querySelectorAll('.chain-step')[2].className = 'chain-step fulfilled';
visual.innerHTML += '<div class="chain-arrow">↓ .finally()</div><div class="chain-step fulfilled">完成! 所有数据获取成功</div>';
resolve(orders);
}, 1000);
});
}).finally(() => {
log.textContent += '\n链式调用完成!';
});
}
function demoChainError() {
const log = document.getElementById('chainLog');
const visual = document.getElementById('chainVisual');
log.textContent = '';
visual.innerHTML = '<div class="chain-step pending">Step 1: fetch用户数据... (pending)</div>';
new Promise(resolve => {
setTimeout(() => {
log.textContent += 'Step 1: 成功\n';
visual.querySelector('.chain-step:first-child').className = 'chain-step fulfilled';
visual.innerHTML += '<div class="chain-arrow">↓ .then()</div><div class="chain-step pending">Step 2: 获取订单数据... (pending)</div>';
resolve({ id: 1 });
}, 1000);
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
log.textContent += 'Step 2: 失败! 服务器错误\n';
visual.querySelectorAll('.chain-step')[1].className = 'chain-step rejected';
visual.innerHTML += '<div class="chain-arrow">↓ .catch()</div><div class="chain-step fulfilled">错误已捕获,显示友好提示</div>';
reject(new Error('服务器错误 500'));
}, 1000);
});
}).catch(error => {
log.textContent += `\n捕获错误: ${error.message}\n错误不会继续向下传递,已被处理`;
});
}
</script>
</body>
</html>示例2:Promise组合方法对比
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Promise组合方法对比</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.method-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
.method-card { padding: 16px; border: 2px solid #e8eaed; border-radius: 10px; cursor: pointer; transition: all 0.2s; }
.method-card:hover { border-color: #1a73e8; }
.method-card.active { border-color: #1a73e8; background: #e3f2fd; }
.method-name { font-weight: 800; font-size: 16px; color: #1a73e8; margin-bottom: 6px; }
.method-desc { font-size: 13px; color: #666; line-height: 1.6; }
.task-config { margin-bottom: 16px; }
.task-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.task-label { font-weight: 600; min-width: 80px; }
.task-select { padding: 6px 12px; border: 1px solid #dadce0; border-radius: 6px; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.btn-primary { background: #1a73e8; color: #fff; }
.result-area { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace; font-size: 13px; line-height: 1.7; min-height: 120px; white-space: pre-wrap; }
.comparison-table { width: 100%; border-collapse: collapse; margin-top: 16px; }
.comparison-table th, .comparison-table td { padding: 10px 14px; text-align: center; border: 1px solid #e8eaed; font-size: 13px; }
.comparison-table th { background: #1a73e8; color: #fff; }
.comparison-table td:first-child { font-weight: 700; text-align: left; }
</style>
</head>
<body>
<div class="container">
<h1>Promise组合方法对比</h1>
<div class="card">
<h2>选择组合方法</h2>
<div class="method-grid">
<div class="method-card active" onclick="selectMethod('all', this)">
<div class="method-name">Promise.all</div>
<div class="method-desc">全部成功才成功,任一失败则失败。适合需要所有结果都成功的场景。</div>
</div>
<div class="method-card" onclick="selectMethod('allSettled', this)">
<div class="method-name">Promise.allSettled</div>
<div class="method-desc">等待全部完成,无论成功失败。适合需要知道每个结果状态的场景。</div>
</div>
<div class="method-card" onclick="selectMethod('race', this)">
<div class="method-name">Promise.race</div>
<div class="method-desc">返回最先完成的结果(成功或失败)。适合超时控制。</div>
</div>
<div class="method-card" onclick="selectMethod('any', this)">
<div class="method-name">Promise.any</div>
<div class="method-desc">返回最先成功的结果,全部失败才失败。适合多源容灾。</div>
</div>
</div>
</div>
<div class="card">
<h2>模拟任务配置</h2>
<div class="task-config">
<div class="task-row"><span class="task-label">任务1</span><select class="task-select" id="task1"><option value="fulfill">成功 (1秒)</option><option value="fulfill-fast">成功 (0.5秒)</option><option value="reject">失败 (1秒)</option><option value="reject-fast">失败 (0.5秒)</option></select></div>
<div class="task-row"><span class="task-label">任务2</span><select class="task-select" id="task2"><option value="fulfill">成功 (1秒)</option><option value="fulfill-fast">成功 (0.5秒)</option><option value="reject">失败 (1秒)</option><option value="reject-fast">失败 (0.5秒)</option></select></div>
<div class="task-row"><span class="task-label">任务3</span><select class="task-select" id="task3"><option value="fulfill">成功 (1秒)</option><option value="fulfill-fast">成功 (0.5秒)</option><option value="reject">失败 (1秒)</option><option value="reject-fast">失败 (0.5秒)</option></select></div>
</div>
<button class="btn-primary" onclick="runDemo()">执行</button>
</div>
<div class="card">
<h2>执行结果</h2>
<div class="result-area" id="result">配置任务后点击"执行"</div>
</div>
<div class="card">
<h2>方法对比表</h2>
<table class="comparison-table">
<thead><tr><th>特性</th><th>Promise.all</th><th>Promise.allSettled</th><th>Promise.race</th><th>Promise.any</th></tr></thead>
<tbody>
<tr><td>成功条件</td><td>全部成功</td><td>永远成功</td><td>最先完成</td><td>任一成功</td></tr>
<tr><td>失败条件</td><td>任一失败</td><td>永不失败</td><td>最先完成的是失败</td><td>全部失败</td></tr>
<tr><td>返回值</td><td>值数组</td><td>状态对象数组</td><td>最先完成的值</td><td>最先成功的值</td></tr>
<tr><td>错误类型</td><td>第一个错误</td><td>无</td><td>第一个错误</td><td>AggregateError</td></tr>
<tr><td>典型场景</td><td>并行请求</td><td>批量操作</td><td>超时控制</td><td>多源容灾</td></tr>
</tbody>
</table>
</div>
</div>
<script>
let currentMethod = 'all';
function selectMethod(method, el) {
currentMethod = method;
document.querySelectorAll('.method-card').forEach(c => c.classList.remove('active'));
el.classList.add('active');
}
function createTask(config, index) {
return new Promise((resolve, reject) => {
const time = config.includes('fast') ? 500 : 1000;
setTimeout(() => {
if (config.includes('fulfill')) {
resolve(`任务${index + 1}成功 (${time}ms)`);
} else {
reject(new Error(`任务${index + 1}失败 (${time}ms)`));
}
}, time);
});
}
async function runDemo() {
const configs = [
document.getElementById('task1').value,
document.getElementById('task2').value,
document.getElementById('task3').value
];
const tasks = configs.map((c, i) => createTask(c, i));
const result = document.getElementById('result');
result.textContent = `使用 Promise.${currentMethod} 执行中...\n\n`;
try {
let output;
switch (currentMethod) {
case 'all':
output = await Promise.all(tasks);
result.textContent += `结果: 全部成功!\n${JSON.stringify(output, null, 2)}`;
break;
case 'allSettled':
output = await Promise.allSettled(tasks);
result.textContent += `结果:\n${JSON.stringify(output, null, 2)}`;
break;
case 'race':
output = await Promise.race(tasks);
result.textContent += `结果: 最先完成!\n${output}`;
break;
case 'any':
output = await Promise.any(tasks);
result.textContent += `结果: 最先成功!\n${output}`;
break;
}
} catch (error) {
if (error instanceof AggregateError) {
result.textContent += `全部失败!\n错误列表:\n${error.errors.map(e => ' ' + e.message).join('\n')}`;
} else {
result.textContent += `失败: ${error.message}`;
}
}
}
</script>
</body>
</html>示例3:并发控制与异步流程
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>并发控制与异步流程</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #f0f2f5; padding: 20px; color: #333; }
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; color: #1a73e8; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #e8eaed; }
.controls { display: flex; gap: 10px; margin-bottom: 16px; align-items: center; flex-wrap: wrap; }
label { font-weight: 600; font-size: 14px; }
input[type="number"], select { padding: 8px 12px; border: 1px solid #dadce0; border-radius: 6px; width: 80px; }
button { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
.btn-blue { background: #1a73e8; color: #fff; }
.btn-green { background: #34a853; color: #fff; }
.btn-orange { background: #ff9800; color: #fff; }
.task-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; margin-bottom: 16px; }
.task-box { padding: 12px 8px; border-radius: 8px; text-align: center; font-size: 13px; font-weight: 600; transition: all 0.3s; }
.task-pending { background: #f8f9fa; color: #999; }
.task-running { background: #e3f2fd; color: #1565c0; animation: pulse 1s infinite; }
.task-done { background: #e8f5e9; color: #2e7d32; }
.task-error { background: #ffebee; color: #c62828; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
.stats { display: flex; gap: 16px; margin-top: 12px; }
.stat { padding: 8px 16px; background: #f8f9fa; border-radius: 6px; font-size: 13px; }
.stat b { color: #1a73e8; }
</style>
</head>
<body>
<div class="container">
<h1>并发控制与异步流程</h1>
<div class="card">
<h2>并发控制演示</h2>
<div class="controls">
<label>任务数:</label><input type="number" id="taskCount" value="10" min="1" max="30">
<label>并发数:</label><input type="number" id="concurrency" value="3" min="1" max="10">
<button class="btn-blue" onclick="runSerial()">串行执行</button>
<button class="btn-green" onclick="runParallel()">并行执行</button>
<button class="btn-orange" onclick="runLimited()">限制并发</button>
</div>
<div class="task-grid" id="taskGrid"></div>
<div class="stats">
<div class="stat">已完成: <b id="doneCount">0</b></div>
<div class="stat">耗时: <b id="elapsed">0</b>ms</div>
<div class="stat">当前并发: <b id="currentConc">0</b></div>
</div>
</div>
</div>
<script>
let tasks = [];
let taskStates = [];
function initTasks() {
const count = parseInt(document.getElementById('taskCount').value);
tasks = Array.from({ length: count }, (_, i) => ({
id: i + 1,
duration: 500 + Math.random() * 1000
}));
taskStates = tasks.map(() => 'pending');
renderGrid();
document.getElementById('doneCount').textContent = '0';
document.getElementById('elapsed').textContent = '0';
document.getElementById('currentConc').textContent = '0';
}
function renderGrid() {
document.getElementById('taskGrid').innerHTML = tasks.map((t, i) => `
<div class="task-box task-${taskStates[i]}" id="task-${i}">
#${t.id}<br><span style="font-size:11px">${Math.round(t.duration)}ms</span>
</div>
`).join('');
}
function updateTask(index, state) {
taskStates[index] = state;
const el = document.getElementById('task-' + index);
if (el) el.className = `task-box task-${state}`;
document.getElementById('doneCount').textContent = taskStates.filter(s => s === 'done').length;
document.getElementById('currentConc').textContent = taskStates.filter(s => s === 'running').length;
}
function simulateTask(task, index) {
return new Promise(resolve => {
updateTask(index, 'running');
setTimeout(() => {
updateTask(index, 'done');
resolve(task.id);
}, task.duration);
});
}
async function runSerial() {
initTasks();
const start = Date.now();
for (let i = 0; i < tasks.length; i++) {
await simulateTask(tasks[i], i);
}
document.getElementById('elapsed').textContent = Date.now() - start;
}
async function runParallel() {
initTasks();
const start = Date.now();
await Promise.all(tasks.map((t, i) => simulateTask(t, i)));
document.getElementById('elapsed').textContent = Date.now() - start;
}
async function runLimited() {
initTasks();
const limit = parseInt(document.getElementById('concurrency').value);
const start = Date.now();
const executing = new Set();
for (let i = 0; i < tasks.length; i++) {
const p = simulateTask(tasks[i], i).then(() => {
executing.delete(p);
});
executing.add(p);
if (executing.size >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
document.getElementById('elapsed').textContent = Date.now() - start;
}
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 始终处理错误
代码示例
// 不好:未处理rejection
promise.then(value => console.log(value));
// 好:添加catch
promise.then(value => console.log(value)).catch(error => console.error(error));2. 避免在Promise构造函数中执行同步操作
代码示例
// 不好
const promise = new Promise(resolve => resolve(someSyncOperation()));
// 好
const result = someSyncOperation();
const promise = Promise.resolve(result);3. 注意Promise.all的短路特性
Promise.all在任一Promise失败时立即reject,其他Promise仍会继续执行但结果被忽略。
4. 合理选择组合方法
需要所有结果:Promise.all
需要每个结果的状态:Promise.allSettled
需要最快结果:Promise.race
需要最快成功结果:Promise.any
5. 控制并发数量
大量并发请求可能导致服务器压力过大,使用并发控制限制同时执行的Promise数量。
代码规范示例
优雅的异步流程控制
代码示例
class AsyncTaskRunner {
constructor(concurrency = 3) {
this.concurrency = concurrency;
}
async run(tasks) {
const results = new Array(tasks.length);
const executing = new Set();
for (let i = 0; i < tasks.length; i++) {
const p = Promise.resolve().then(() => tasks[i]()).then(result => {
executing.delete(p);
results[i] = { status: 'fulfilled', value: result };
}).catch(error => {
executing.delete(p);
results[i] = { status: 'rejected', reason: error };
});
executing.add(p);
if (executing.size >= this.concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
}
// 使用
const runner = new AsyncTaskRunner(3);
const results = await runner.run([
() => fetch('/api/users').then(r => r.json()),
() => fetch('/api/orders').then(r => r.json()),
() => fetch('/api/products').then(r => r.json()),
// ...更多任务
]);常见问题与解决方案
问题1:未处理的Promise rejection
解决方案:全局监听unhandledrejection事件:
代码示例
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的Promise rejection:', event.reason);
event.preventDefault();
});问题2:Promise.all一个失败全部失败
解决方案:使用Promise.allSettled获取所有结果,或为每个Promise添加catch:
代码示例
const results = await Promise.all(
promises.map(p => p.catch(error => ({ error })))
);问题3:Promise无法取消
解决方案:虽然Promise本身无法取消,但可以配合AbortController取消fetch请求,或使用第三方库如Bluebird。
问题4:串行执行效率低
解决方案:使用Promise.all并行执行独立任务,使用并发控制处理依赖关系。
总结
Promise是现代JavaScript异步编程的基石:
三种状态:pending → fulfilled/rejected,状态一旦确定不可改变
链式调用:then/catch/finally实现扁平化的异步流程
组合方法:all(全部成功)、allSettled(全部完成)、race(最快完成)、any(最快成功)
错误处理:catch捕获、错误冒泡、全局unhandledrejection
并发控制:限制同时执行的Promise数量,避免资源耗尽
流程控制:串行、并行、限制并发三种执行模式
掌握Promise的核心概念和最佳实践,是理解async/await和构建复杂异步应用的基础。
常见问题
什么是Promise与异步编程?
Promise与异步编程是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习Promise与异步编程的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
Promise与异步编程有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
Promise与异步编程适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
Promise与异步编程的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别