ES6 Promise 完全指南:从入门到精通

一、Promise 是什么?

Promise 是 ES6 引入的异步编程的解决方案,用于解决传统的"回调地狱"问题。你可以把它想象成一个"承诺"------我现在给你一个承诺,将来要么成功(resolve),要么失败(reject)。

1.1 为什么需要 Promise?

先看看"回调地狱"的样子:

javascript 复制代码
// 回调地狱示例
getUser(1, function(user) {
    getOrders(user.id, function(orders) {
        getOrderDetails(orders[0].id, function(details) {
            getProductInfo(details.productId, function(product) {
                console.log(product);
                // 如果还有更多异步操作... 代码会向右无限延伸
            });
        });
    });
});

二、Promise 基础

2.1 创建 Promise

javascript 复制代码
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
    // 异步操作,比如读取文件、请求API等
    
    const success = true; // 假设操作成功
    
    if (success) {
        resolve('操作成功!'); // 承诺兑现
    } else {
        reject('操作失败!'); // 承诺拒绝
    }
});

// 使用 Promise
promise
    .then(result => {
        console.log('成功:', result);
    })
    .catch(error => {
        console.log('失败:', error);
    });

2.2 Promise 的三种状态

  1. pending(等待中):初始状态

  2. fulfilled(已成功):操作成功完成

  3. rejected(已失败):操作失败

javascript 复制代码
const promise = new Promise((resolve, reject) => {
    console.log('Promise 创建,状态:pending');
    
    setTimeout(() => {
        resolve('成功数据');
        console.log('状态变为:fulfilled');
    }, 1000);
});

promise.then(data => {
    console.log('接收到数据:', data);
});

三、Promise 的核心方法

3.1 .then() - 处理成功

javascript 复制代码
const promise = new Promise(resolve => {
    setTimeout(() => resolve('数据1'), 1000);
});

promise.then(
    // 第一个参数:成功回调
    data => {
        console.log('第一次处理:', data);
        return data + ' -> 处理后的数据';
    },
    // 第二个参数:失败回调(不推荐用这个,推荐用.catch)
    error => {
        console.error('错误:', error);
    }
).then(newData => {
    console.log('第二次处理:', newData);
    // 输出:第二次处理: 数据1 -> 处理后的数据
});

3.2 .catch() - 处理失败

javascript 复制代码
const promise = new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('网络错误')), 1000);
});

promise
    .then(data => {
        console.log('成功:', data);
    })
    .catch(error => {
        console.error('捕获到错误:', error.message);
        // 可以返回新值,继续链式调用
        return '默认值';
    })
    .then(data => {
        console.log('继续执行:', data);
        // 输出:继续执行: 默认值
    });

3.3 .finally() - 无论成功失败都会执行

javascript 复制代码
function loadData() {
    return new Promise((resolve, reject) => {
        const success = Math.random() > 0.5;
        setTimeout(() => {
            success ? resolve('数据加载成功') : reject('加载失败');
        }, 1000);
    });
}

loadData()
    .then(data => console.log(data))
    .catch(error => console.error(error))
    .finally(() => {
        console.log('无论成功失败,我都会执行');
        // 通常用于清理工作,如隐藏loading图标
    });

四、Promise 链式调用

这是 Promise 最强大的特性!

javascript 复制代码
// 模拟异步操作
function delay(ms, value) {
    return new Promise(resolve => {
        setTimeout(() => resolve(value), ms);
    });
}

// 链式调用示例
delay(1000, '第一步')
    .then(result => {
        console.log(result);
        return delay(500, '第二步');
    })
    .then(result => {
        console.log(result);
        return delay(300, '第三步');
    })
    .then(result => {
        console.log(result);
        return '所有步骤完成!';
    })
    .then(finalResult => {
        console.log(finalResult);
    });

// 等价于(如果不用Promise):
// setTimeout(() => {
//     console.log('第一步');
//     setTimeout(() => {
//         console.log('第二步');
//         setTimeout(() => {
//             console.log('第三步');
//             console.log('所有步骤完成!');
//         }, 300);
//     }, 500);
// }, 1000);

五、Promise 静态方法

5.1 Promise.resolve() - 创建已成功的 Promise

javascript 复制代码
// 创建一个立即成功的 Promise
Promise.resolve('立即成功')
    .then(data => console.log(data));
// 输出:立即成功

// 等价于
new Promise(resolve => resolve('立即成功'));

// 传入 Promise 对象
const originalPromise = new Promise(resolve => resolve('原始'));
Promise.resolve(originalPromise)
    .then(data => console.log(data)); // 输出:原始

5.2 Promise.reject() - 创建已失败的 Promise

javascript 复制代码
// 创建一个立即失败的 Promise
Promise.reject(new Error('立即失败'))
    .catch(error => console.error(error.message));
// 输出:立即失败

5.3 Promise.all() - 等待所有 Promise 完成

javascript 复制代码
const p1 = Promise.resolve('任务1');
const p2 = new Promise(resolve => 
    setTimeout(() => resolve('任务2'), 1000)
);
const p3 = fetch('https://api.example.com/data'); // 假设返回Promise

// 所有Promise都成功才成功
Promise.all([p1, p2, p3])
    .then(results => {
        console.log('所有任务完成:', results);
        // results 是一个数组,包含每个Promise的结果
        // 顺序与传入的数组顺序一致
    })
    .catch(error => {
        // 任意一个失败,整个Promise.all就失败
        console.error('有任务失败:', error);
    });

// 实际例子:同时请求多个API
const userId = 1;
Promise.all([
    fetch(`/api/users/${userId}`),
    fetch(`/api/users/${userId}/orders`),
    fetch(`/api/users/${userId}/profile`)
])
.then(([user, orders, profile]) => {
    // 处理所有数据
})
.catch(error => console.error('请求失败', error));

5.4 Promise.race() - 竞速,第一个完成(无论成功失败)的 Promise

javascript 复制代码
const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('请求超时')), 5000)
);

const apiRequest = fetch('https://api.example.com/data');

// 谁先完成就用谁的结果
Promise.race([apiRequest, timeout])
    .then(data => {
        console.log('API请求成功');
    })
    .catch(error => {
        console.error('请求超时或失败');
    });

5.5 Promise.allSettled() - 等待所有 Promise 完成(无论成功失败)

javascript 复制代码
const promises = [
    Promise.resolve('成功1'),
    Promise.reject('失败1'),
    Promise.resolve('成功2')
];

Promise.allSettled(promises)
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index}: 成功`, result.value);
            } else {
                console.log(`Promise ${index}: 失败`, result.reason);
            }
        });
    });

// 输出:
// Promise 0: 成功 成功1
// Promise 1: 失败 失败1
// Promise 2: 成功 成功2

5.6 Promise.any() - 第一个成功的 Promise(ES2021)

javascript 复制代码
const promises = [
    Promise.reject('错误1'),
    new Promise(resolve => setTimeout(() => resolve('成功1'), 1000)),
    new Promise(resolve => setTimeout(() => resolve('成功2'), 500))
];

Promise.any(promises)
    .then(firstSuccess => {
        console.log('第一个成功的:', firstSuccess);
        // 输出:第一个成功的: 成功2
    })
    .catch(error => {
        // 只有所有Promise都失败才进入catch
        console.error('全部失败');
    });

六、Promise 错误处理

6.1 错误冒泡

javascript 复制代码
// 错误会一直向后传递,直到被catch捕获
Promise.resolve()
    .then(() => {
        throw new Error('第一步出错');
    })
    .then(() => {
        console.log('这里不会执行');
    })
    .catch(error => {
        console.error('捕获到错误:', error.message);
        // 可以重新抛出
        // throw error;
    });

// 多个catch的情况
new Promise((resolve, reject) => {
    reject('初始错误');
})
.catch(error => {
    console.log('第一个catch:', error);
    throw '新错误';
})
.catch(error => {
    console.log('第二个catch:', error);
    return '恢复正常';
})
.then(data => {
    console.log('恢复正常后:', data);
});

6.2 异步错误处理

javascript 复制代码
// 错误示例:无法捕获异步错误
try {
    new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('异步错误');
        }, 1000);
    });
} catch (error) {
    // 这里捕获不到!
    console.error('捕获不到:', error);
}

// 正确做法
new Promise((resolve, reject) => {
    setTimeout(() => {
        try {
            // 可能出错的代码
            throw new Error('异步错误');
        } catch (error) {
            reject(error);
        }
    }, 1000);
})
.catch(error => {
    console.error('正确捕获:', error.message);
});

七、Promise 实际应用场景

7.1 封装回调函数为 Promise

javascript 复制代码
// 将setTimeout封装为Promise
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 使用
delay(1000)
    .then(() => console.log('1秒后执行'));

// 封装fs.readFile(Node.js)
const fs = require('fs');
function readFilePromise(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf8', (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

// 使用
readFilePromise('./file.txt')
    .then(content => console.log(content))
    .catch(error => console.error(error));

7.2 顺序执行异步操作

javascript 复制代码
// 用户注册流程
function registerUser(userData) {
    return validateUser(userData)
        .then(() => checkEmailExists(userData.email))
        .then(() => createUser(userData))
        .then(user => sendWelcomeEmail(user))
        .then(() => logRegistration(userData))
        .catch(error => {
            console.error('注册失败:', error);
            throw error;
        });
}

// 模拟各个步骤
function validateUser(data) {
    return new Promise((resolve, reject) => {
        if (!data.email) reject('邮箱不能为空');
        else resolve();
    });
}

function checkEmailExists(email) {
    return delay(300).then(() => {
        // 模拟数据库查询
        const exists = Math.random() > 0.5;
        if (exists) throw '邮箱已存在';
    });
}

function createUser(data) {
    return delay(500).then(() => ({
        id: Date.now(),
        ...data
    }));
}

7.3 控制并发数量

javascript 复制代码
// 限制并发数量的Promise处理
async function processWithConcurrency(tasks, concurrency = 3) {
    const results = [];
    const executing = [];
    
    for (const task of tasks) {
        const p = Promise.resolve().then(() => task());
        results.push(p);
        
        const e = p.then(() => executing.splice(executing.indexOf(e), 1));
        executing.push(e);
        
        if (executing.length >= concurrency) {
            await Promise.race(executing);
        }
    }
    
    return Promise.all(results);
}

// 使用
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const tasks = urls.map(url => () => fetch(url));

processWithConcurrency(tasks, 2)
    .then(responses => console.log('所有请求完成'))
    .catch(error => console.error(error));

八、Promise 常见陷阱和最佳实践

8.1 常见陷阱

javascript 复制代码
// 陷阱1:忘记return
Promise.resolve(1)
    .then(value => {
        value + 1; // 忘记return!
    })
    .then(value => {
        console.log(value); // undefined
    });

// 陷阱2:在then中抛出错误但不处理
Promise.resolve()
    .then(() => {
        throw new Error('未处理的错误');
    }); // 没有catch,错误会静默失败

// 陷阱3:嵌套Promise
// 错误写法
promise.then(result => {
    anotherPromise.then(anotherResult => {
        // 回调地狱又回来了!
    });
});

// 正确写法
promise
    .then(result => anotherPromise)
    .then(anotherResult => {
        // 扁平化的链式调用
    });

8.2 最佳实践

javascript 复制代码
// 1. 总是使用catch
someAsyncFunction()
    .then(handleSuccess)
    .catch(handleError); // 不要省略!

// 2. 使用async/await(ES8)让代码更清晰
async function getUserData(userId) {
    try {
        const user = await fetchUser(userId);
        const orders = await fetchOrders(user.id);
        const profile = await fetchProfile(user.id);
        return { user, orders, profile };
    } catch (error) {
        console.error('获取用户数据失败:', error);
        throw error;
    }
}

// 3. 给Promise命名,便于调试
const userPromise = fetchUser(1);
const orderPromise = fetchOrders(1);

Promise.all([userPromise, orderPromise])
    .then(([user, orders]) => {
        // 清晰的命名
    });

// 4. 避免在Promise构造函数中写太多逻辑
// 不好
const promise = new Promise((resolve, reject) => {
    // 几十行业务逻辑...
});

// 好
function doComplexTask() {
    // 复杂逻辑放在外部函数
    return prepareData()
        .then(processData)
        .then(validateData);
}

九、Promise 面试题解析

9.1 基础题

javascript 复制代码
// 问题:输出顺序是什么?
console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve()
    .then(() => console.log('3'))
    .then(() => console.log('4'));

console.log('5');

// 答案:1 5 3 4 2
// 解析:同步代码 > 微任务(Promise) > 宏任务(setTimeout)

9.2 进阶题

javascript 复制代码
// 问题:输出顺序是什么?
Promise.resolve()
    .then(() => {
        console.log('then1');
        Promise.resolve()
            .then(() => console.log('then1-1'))
            .then(() => console.log('then1-2'));
    })
    .then(() => console.log('then2'));

// 答案:then1 → then1-1 → then2 → then1-2
// 解析:Promise链的微妙执行顺序

十、总结

Promise 的核心优势:

  1. 链式调用:解决回调地狱

  2. 错误统一处理 :通过 .catch() 统一处理错误

  3. 状态可追踪:明确知道异步操作的状态

  4. 组合能力强 :通过 Promise.all()Promise.race() 等组合多个异步操作

现代异步编程的发展:

javascript 复制代码
// 1. 回调函数时代
readFile('a.txt', (err, data) => {
    if (err) handleError(err);
    else process(data);
});

// 2. Promise时代
readFilePromise('a.txt')
    .then(process)
    .catch(handleError);

// 3. Async/Await时代(基于Promise)
async function processFile() {
    try {
        const data = await readFilePromise('a.txt');
        return process(data);
    } catch (error) {
        handleError(error);
    }
}

记住:Promise 不是万能的,但它为异步编程提供了一个坚实的基础。理解 Promise 的工作原理,会让你更好地使用 async/await 等更高级的语法糖。

相关推荐
AC赳赳老秦1 天前
前端可视化组件开发:DeepSeek辅助Vue/React图表组件编写实战
前端·vue.js·人工智能·react.js·信息可视化·数据分析·deepseek
小白冲鸭1 天前
苍穹外卖-前端环境搭建-nginx双击后网页打不开
运维·前端·nginx
wulijuan8886661 天前
Web Worker
前端·javascript
深念Y1 天前
仿B站项目 前端 3 首页 整体结构
前端·ai·vue·agent·bilibili·首页
IT_陈寒1 天前
React 18实战:这5个新特性让我的开发效率提升了40%
前端·人工智能·后端
深念Y1 天前
仿B站项目 前端 5 首页 标签栏
前端·vue·ai编程·bilibili·标签栏·trae·滚动栏
老朋友此林1 天前
React Hook原理速通笔记1(useEffect 原理、使用踩坑、渲染周期、依赖项)
javascript·笔记·react.js
克里斯蒂亚诺更新1 天前
vue3使用pinia替代vuex举例
前端·javascript·vue.js
Benny的老巢1 天前
用 Playwright 启动指定 Chrome 账号的本地浏览器, 复用原账号下的cookie信息
前端·chrome