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是一个代表异步操作最终完成或失败的对象。它有三种状态:

状态 说明 转换
pending(待定) 初始状态 可转为fulfilled或rejected
fulfilled(已完成) 操作成功 不可再变
rejected(已拒绝) 操作失败 不可再变

代码示例

// 创建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的返回值规则

返回值类型 后续Promise状态
普通值 fulfilled,值为返回值
新的Promise 取决于新Promise的结果
throw错误 rejected,错误为抛出值

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);
}

组合方法对比:

方法 成功条件 失败条件 返回值
all 全部成功 任一失败 结果数组
allSettled 永远成功 永不失败 状态对象数组
race 最先完成(成功或失败) 最先完成的是失败 最先完成的值
any 任一成功 全部失败 最先成功的值

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>

浏览器兼容性

特性 Chrome Firefox Safari Edge 说明
Promise 33+ 29+ 7.1+ 12+ 基本功能
Promise.all 33+ 29+ 7.1+ 12+ 全部成功
Promise.race 33+ 29+ 7.1+ 12+ 竞速
Promise.allSettled 76+ 71+ 13+ 79+ 全部完成
Promise.any 85+ 79+ 14.1+ 85+ 任一成功
Promise.prototype.finally 63+ 58+ 11.1+ 18+ 最终处理
AggregateError 85+ 79+ 14.1+ 85+ Promise.any错误

注意事项与最佳实践

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异步编程的基石:

  1. 三种状态:pending → fulfilled/rejected,状态一旦确定不可改变

  2. 链式调用:then/catch/finally实现扁平化的异步流程

  3. 组合方法:all(全部成功)、allSettled(全部完成)、race(最快完成)、any(最快成功)

  4. 错误处理:catch捕获、错误冒泡、全局unhandledrejection

  5. 并发控制:限制同时执行的Promise数量,避免资源耗尽

  6. 流程控制:串行、并行、限制并发三种执行模式

掌握Promise的核心概念和最佳实践,是理解async/await和构建复杂异步应用的基础。

常见问题

什么是Promise与异步编程?

Promise与异步编程是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。

如何学习Promise与异步编程的实际应用?

教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。

Promise与异步编程有哪些注意事项?

常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。

Promise与异步编程适合初学者吗?

本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。

Promise与异步编程的核心要点是什么?

核心要点包括教程简介等内容,建议按教程顺序逐步学习。

标签: Promise 容错 语法糖 async await 异步函数 场景管理 重试机制 回调地狱

本文由小确幸生活整理发布,转载请注明出处

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:Fetch API详解 - 从入门到实践详解 下一篇: 后端通信:async/await - 从入门到实践详解

poll相关推荐