Day-25-异步编程Promise

Day 25: 异步编程Promise

📅 日期: 2026年03月18日
🎯 学习目标: 深入理解异步编程与 Promise,掌握异步操作的最佳实践
⏱️ 预计用时: 2-3 小时
📂 分类: 10-现代JavaScript


🎯 学习目标

通过今天的学习,你将:

  1. 理解异步编程模型:事件循环、回调队列
  2. 掌握 Promise 用法:创建、链式调用、错误处理
  3. 学会 async/await:异步代码的同步写法
  4. 实践并行处理:Promise.all、race、allSettled
  5. 应用最佳实践:错误处理、性能优化

💡 核心概念

1. 同步 vs 异步

// 同步代码:阻塞执行
console.log('1');
const data = readFile('data.txt');  // 阻塞,等待文件读取完成
console.log('2');
console.log(data);
// 输出:1 -> 数据 -> 2

// 异步代码:非阻塞执行
console.log('1');
readFile('data.txt', (err, data) => {
    console.log(data);
});
console.log('2');
// 输出:1 -> 2 -> 数据

2. 回调函数的问题

// 回调地狱(Callback Hell)
getData(id, (data) => {
    getMoreData(data.id, (moreData) => {
        getEvenMoreData(moreData.id, (evenMoreData) => {
            // 继续嵌套...
        });
    });
});

3. Promise 基础

// 创建 Promise
const promise = new Promise((resolve, reject) => {
    // 异步操作
    const success = true;
    
    if (success) {
        resolve('操作成功');  // 成功时调用
    } else {
        reject('操作失败');   // 失败时调用
    }
});

// 使用 Promise
promise
    .then(result => console.log(result))
    .catch(error => console.error(error));

🛠️ API 函数详解

1. Promise 构造函数

const myPromise = new Promise((resolve, reject) => {
    // executor 函数:执行异步操作
    // resolve:成功时调用
    // reject:失败时调用
    
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            resolve('数据加载成功');
        } else {
            reject(new Error('数据加载失败'));
        }
    }, 1000);
});

2. Promise 实例方法

// then():处理成功状态
promise.then(value => {
    console.log('成功:', value);
    return value + 1;  // 返回值传递给下一个 then
}).then(newValue => {
    console.log('新值:', newValue);
});

// catch():处理失败状态
promise.catch(error => {
    console.error('错误:', error.message);
});

// finally():无论成功失败都执行
promise.finally(() => {
    console.log('清理资源');
});

3. Promise 静态方法

// Promise.all:所有 Promise 都成功才成功
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
    .then(values => {
        console.log(values);  // [1, 2, 3]
    })
    .catch(error => {
        console.error('至少一个失败:', error);
    });

// Promise.race:第一个完成的 Promise 结果
const fast = new Promise(resolve => setTimeout(() => resolve('快'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('慢'), 200));

Promise.race([fast, slow])
    .then(value => console.log(value));  // '快'

// Promise.allSettled:返回所有 Promise 的结果
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);

Promise.allSettled([p1, p2])
    .then(results => {
        console.log(results);
        // [
        //   { status: 'fulfilled', value: 1 },
        //   { status: 'rejected', reason: 2 }
        // ]
    });

🎮 实战示例

示例 1:封装异步操作

// 封装 fetch 为 Promise
function fetchJSON(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                if (!response.ok) {
                    throw new Error('HTTP ' + response.status);
                }
                return response.json();
            })
            .then(data => resolve(data))
            .catch(error => reject(error));
    });
}

// 使用
fetchJSON('/api/data')
    .then(data => console.log(data))
    .catch(error => console.error(error));

示例 2:异步文件操作

// 读取文件的 Promise 版本(Node.js)
function readFilePromise(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf8', (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

// 使用
readFilePromise('./data.txt')
    .then(data => console.log(data))
    .catch(err => console.error('读取失败:', err));

示例 3:顺序执行多个异步操作

// 顺序执行:使用 then 链
function getUser(id) {
    return fetch('/api/users/' + id).then(r => r.json());
}

function getPosts(userId) {
    return fetch('/api/users/' + userId + '/posts').then(r => r.json());
}

function getComments(postId) {
    return fetch('/api/posts/' + postId + '/comments').then(r => r.json());
}

// 依次执行
getUser(1)
    .then(user => getPosts(user.id))
    .then(posts => getComments(posts[0].id))
    .then(comments => console.log(comments))
    .catch(error => console.error(error));

⚠️ 重要注意事项

1. 总是返回或传递 Promise

// 正确:总是返回 Promise
function getData() {
    return fetch('/api/data').then(r => r.json());
}

// 错误:不返回 Promise
function getData() {
    fetch('/api/data').then(r => r.json());
    // 调用者无法获取结果
}

2. 总是处理错误

// 正确:总是处理错误
promise
    .then(data => processData(data))
    .catch(error => handleError(error));

// 错误:不处理错误
promise.then(data => processData(data));
    // 错误会变成未捕获的 Promise rejection

3. 避免嵌套 Promise

// 正确:扁平化 then 链
promise1
    .then(result1 => {
        return promise2(result1);
    })
    .then(result2 => {
        return promise3(result2);
    });

// 错误:嵌套 Promise
promise1.then(result1 => {
    promise2(result1).then(result2 => {
        promise3(result2).then(result3 => {
            // 又是回调地狱
        });
    });
});

4. Promise 状态不可逆

const promise = new Promise((resolve) => {
    resolve('第一次');
    resolve('第二次');  // 无效,Promise 状态不会改变
    reject('失败');      // 无效
});

promise.then(console.log);  // 只输出 '第一次'

🎓 实战应用场景

场景 1: API 请求

// 并行请求多个 API
async function getDashboardData() {
    const [users, posts, comments] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/comments').then(r => r.json())
    ]);
    
    return { users, posts, comments };
}

场景 2: 超时控制

// Promise.race 实现超时
function fetchWithTimeout(url, timeout = 5000) {
    const fetchPromise = fetch(url);
    const timeoutPromise = new Promise((_, reject) =>
        setTimeout(() => reject(new Error('请求超时')), timeout)
    );
    
    return Promise.race([fetchPromise, timeoutPromise]);
}

场景 3: 图片预加载

// Promise.all 预加载多张图片
function preloadImages(urls) {
    const promises = urls.map(url => {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(url);
            img.onerror = () => reject(new Error('加载失败: ' + url));
            img.src = url;
        });
    });
    
    return Promise.all(promises);
}

✍️ 练习任务

基础练习(必做)

  1. Promise 创建

    • [ ] 创建一个延迟 2 秒后返回的 Promise
    • [ ] 创建一个随机成功/失败的 Promise
    • [ ] 使用 then/catch 处理结果
  2. 链式调用

    • [ ] 实现至少 3 个 then 的链式调用
    • [ ] 在 then 中返回新的 Promise
    • [ ] 使用 finally 清理资源

进阶练习(推荐)

  1. 并行操作

    • 使用 Promise.all 并行请求 3 个 API
    • 使用 Promise.race 实现请求超时
    • 使用 Promise.allSettled 处理部分失败
  2. 错误处理

    • 实现统一的错误处理函数
    • 区分不同类型的错误
    • 提供友好的错误提示
  3. 工具函数

    // 延迟函数
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // 重试函数
    async function retry(fn, maxRetries = 3) {
        for (let i = 0; i < maxRetries; i++) {
            try {
                return await fn();
            } catch (error) {
                if (i === maxRetries - 1) throw error;
                await delay(1000 * (i + 1));
            }
        }
    }
    

实战挑战(选做)

  1. 实现 Promise 方法

    • Promise.map:并行处理数组
    • Promise.filter:并行过滤数组
    • Promise.reduce:并行归约数组
  2. 取消机制

    • 实现 AbortController
    • 在超时或取消时清理资源
  3. 进度追踪

    • 报告多个 Promise 的完成进度
    • 实现进度条显示

💡 常见问题 FAQ

Q1: 如何调试 Promise 链?

A:

// 在 then 中添加日志
promise
    .then(data => {
        console.log('Step 1:', data);
        return nextStep(data);
    })
    .then(data => {
        console.log('Step 2:', data);
        return finalStep(data);
    });

Q2: 如何处理多个独立的错误?

A:

// 每个操作独立错误处理
const promises = tasks.map(task =>
    task().catch(error => ({ error, task }))
);

const results = await Promise.all(promises);

Q3: Promise 和 async/await 有什么区别?

A:

  • async/await 是 Promise 的语法糖
  • async/await 让异步代码看起来像同步代码
  • 底层仍然使用 Promise
  • 可以混用 Promise 和 async/await

Q4: 如何避免未捕获的 Promise rejection?

A:

// 全局未捕获 rejection 处理(Node.js)
process.on('unhandledRejection', (reason, promise) => {
    console.error('未捕获的 Promise rejection:', reason);
});

// 或在浏览器中
window.addEventListener('unhandledrejection', (event) => {
    console.error('未捕获的 Promise rejection:', event.reason);
    event.preventDefault();  // 阻止默认的控制台错误
});

📚 拓展阅读

官方文档

推荐资源

  1. You Don’t Know JS – async & performance
  2. Promise 详解
  3. JavaScript Async Patterns

相关工具


🎉 总结

今天我们深入学习了 异步编程与 Promise,涵盖了:

异步编程模型:理解事件循环和非阻塞 I/O
Promise 用法:创建、链式调用、错误处理
并行处理:Promise.all、race、allSettled
最佳实践:错误处理、性能优化
实战应用:API 请求、超时控制、图片预加载

Promise 是现代 JavaScript 异步编程的基础,掌握它对于理解 async/await 和更复杂的异步模式至关重要。


下一节课预告: Day 26 将学习 模块化开发与构建工具,学习如何组织大型项目!

💪 加油!坚持就是胜利!