小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-05 / 1 阅读
0
0

JavaScript 异步编程完全指南 - 小白也能懂

JavaScript 异步编程完全指南 - 小白也能懂

一、核心概念:Promise 是什么?

1.1 生活化比喻

// Promise 就像外卖订单
const orderPromise = 点外卖("红烧肉"); 

// 这时候你拿到的是"订单号",不是"红烧肉"
console.log(orderPromise); // "订单号:12345"

// 只有等外卖送到(await),才能吃到红烧肉
const food = await orderPromise; // 等待外卖送达
console.log(food); // "香喷喷的红烧肉"

1.2 Promise 的三种状态

// 1. 等待中 (pending) - 外卖已下单,正在制作
const promise = new Promise((resolve, reject) => {
    // 这时候状态是 pending
});

// 2. 已完成 (fulfilled) - 外卖送达
resolve("数据获取成功"); // 状态变为 fulfilled

// 3. 已拒绝 (rejected) - 外卖取消或失败
reject("网络错误"); // 状态变为 rejected

二、async/await:让异步变简单

2.1 基本用法

// 传统 Promise 写法(复杂)
function getData() {
    return fetch('/api/data')
        .then(response => response.json())
        .then(data => {
            console.log(data);
            return data;
        })
        .catch(error => {
            console.error(error);
        });
}

// async/await 写法(简单直观)
async function getData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        console.log(data);
        return data;
    } catch (error) {
        console.error(error);
    }
}

2.2 async 函数的特点

// async 函数永远返回 Promise
async function hello() {
    return "Hello World";
}

// 等价于
function hello() {
    return Promise.resolve("Hello World");
}

// 使用方式
hello().then(result => console.log(result)); // "Hello World"
// 或者
const result = await hello(); // "Hello World"

三、重点难点详解

3.1 await 到底在等什么?

// ❌ 常见误解:await 是在"阻塞"代码
// ✅ 正确理解:await 是在"等待Promise兑现"

async function example() {
    console.log("开始点外卖");
  
    // 不是"阻塞",而是"登记回调,等外卖到了再继续"
    const food = await 点外卖();
  
    console.log("吃到外卖了"); // 这行代码要等外卖到了才执行
}

3.2 为什么 Promise.all 需要 await?

// 情景:同时点三份外卖
async function 点大餐() {
    // ❌ 错误写法:不要外卖单,直接就要吃
    const [饭, 菜, 汤] = Promise.all([
        点米饭(), 点红烧肉(), 点番茄汤()
    ]);
    // 结果:饭、菜、汤 都是 undefined!
  
    // ✅ 正确写法:等所有外卖都到了再分配
    const [饭, 菜, 汤] = await Promise.all([
        点米饭(), 点红烧肉(), 点番茄汤()
    ]);
    // 结果:真的能吃到饭、菜、汤!
}

3.3 错误处理对比

// Promise 的错误处理
function getData() {
    return fetch('/api')
        .then(response => response.json())
        .catch(error => {
            console.log('错误了:', error);
        });
}

// async/await 的错误处理
async function getData() {
    try {
        const response = await fetch('/api');
        const data = await response.json();
        return data;
    } catch (error) {
        console.log('错误了:', error);
    }
}

四、实际应用场景

4.1 场景1:用户注册流程

async function 用户注册(用户信息) {
    try {
        // 1. 验证用户信息
        const 验证结果 = await 验证用户信息(用户信息);
    
        // 2. 检查用户名是否重复
        const 用户名可用 = await 检查用户名(用户信息.用户名);
    
        // 3. 创建用户账号
        const 用户ID = await 创建用户(用户信息);
    
        // 4. 发送欢迎邮件(不阻塞主流程)
        发送欢迎邮件(用户ID).catch(错误 => {
            console.log('邮件发送失败,但不影响注册:', 错误);
        });
    
        return { 成功: true, 用户ID };
    
    } catch (错误) {
        console.log('注册失败:', 错误);
        return { 成功: false, 错误信息: 错误.message };
    }
}

4.2 场景2:商品详情页数据加载

async function 加载商品页(商品ID) {
    try {
        // 并行加载所有数据(同时点多个外卖)
        const [商品信息, 用户评论, 推荐商品, 库存状态] = await Promise.all([
            获取商品信息(商品ID),
            获取商品评论(商品ID),
            获取推荐商品(商品ID),
            检查库存(商品ID)
        ]);
    
        // 所有数据都到了,渲染页面
        渲染页面({
            商品: 商品信息,
            评论: 用户评论,
            推荐: 推荐商品,
            库存: 库存状态
        });
    
    } catch (错误) {
        // 任何一个请求失败都会到这里
        console.log('页面加载失败:', 错误);
        显示错误页面();
    }
}

五、常见坑点及解决方案

5.1 坑点1:在循环中错误使用 await

// ❌ 错误:一个个等,太慢了
async function 慢速操作() {
    for (const id of [1, 2, 3, 4, 5]) {
        const 数据 = await 获取数据(id); // 等完第一个再等第二个...
    }
}

// ✅ 正确:一起等,速度快
async function 快速操作() {
    const 所有数据 = await Promise.all([
        获取数据(1), 获取数据(2), 获取数据(3), 获取数据(4), 获取数据(5)
    ]);
}

5.2 坑点2:忘记处理错误

// ❌ 危险:错误会默默消失
async function 危险函数() {
    const 数据 = await 获取数据(); // 如果出错,程序会崩溃!
}

// ✅ 安全:妥善处理错误
async function 安全函数() {
    try {
        const 数据 = await 获取数据();
        return 数据;
    } catch (错误) {
        console.log('出错了,但程序不会崩溃:', 错误);
        return 默认数据;
    }
}

5.3 坑点3:在非 async 函数中使用 await

// ❌ 错误:普通函数不能用 await
function 普通函数() {
    const 数据 = await 获取数据(); // SyntaxError!
}

// ✅ 正确:只有 async 函数能用 await
async function 异步函数() {
    const 数据 = await 获取数据(); // 正确
}

六、性能优化技巧

6.1 并行 vs 串行

// 串行:总共需要 3秒
async function 串行请求() {
    const 用户 = await 获取用户(1);    // 等1秒
    const 订单 = await 获取订单(用户.id); // 再等1秒  
    const 地址 = await 获取地址(用户.id); // 再等1秒
}

// 并行:总共只需要 1秒
async function 并行请求() {
    const [用户, 订单, 地址] = await Promise.all([
        获取用户(1),           // 同时开始
        获取订单(1),           // 同时开始  
        获取地址(1)            // 同时开始
    ]);
    // 总共只等最慢的那个(1秒)
}

6.2 错误处理优化

// 方法1:传统 try-catch
async function 方法1() {
    try {
        const 数据 = await 不可靠的API();
        return 数据;
    } catch {
        return 默认值;
    }
}

// 方法2:使用 .catch()
async function 方法2() {
    const 数据 = await 不可靠的API().catch(() => 默认值);
    return 数据;
}

七、记忆口诀

7.1 核心口诀

见到 Promise,就要想 await
没有 await,数据就不在

多个请求并行发,Promise.all 来打包
错误处理不能忘,try-catch 保平安

7.2 使用流程

1. 函数前加 async
2. Promise 前加 await  
3. 多个请求用 Promise.all
4. 错误处理用 try-catch
5. 返回结果自动包装成 Promise

八、实战检查清单

下次写异步代码时,问自己:

  • 函数前面加 async 了吗?
  • Promise 前面加 await 了吗?
  • 多个请求用 Promise.all 优化了吗?
  • 错误处理写了吗?
  • 返回的是实际数据还是 Promise?

掌握了这些,你就真正理解 JavaScript 异步编程了!

不是的! resolvereject 不是自定义函数,而是 JavaScript 引擎提供的参数

它们是 Promise 构造函数的参数

new Promise((resolve, reject) => {
  // ↑           ↑       ↑
  // 这些是 JavaScript 引擎自动传入的函数
  // 我们只是在这里接收和使用它们
});

实际工作原理

// JavaScript 引擎内部大致是这样处理的:
class Promise {
  constructor(executor) {
    // 引擎预先定义好的函数
    const resolve = (value) => { /* 处理成功逻辑 */ };
    const reject = (reason) => { /* 处理失败逻辑 */ };
  
    // 把这两个函数作为参数传给我们写的函数
    executor(resolve, reject);
  }
}

// 我们使用时:
new Promise((我们自己起的resolve参数名, 我们自己起的reject参数名) => {
  // 这里可以任意命名,但通常叫 resolve/reject
  我们自己起的resolve参数名("成功!");
  我们自己起的reject参数名("失败!");
});

参数名可以自定义(但不推荐)

// 虽然可以自定义名字,但不要这样做(容易混淆)
new Promise((成功, 失败) => {
  if (Math.random() > 0.5) {
    成功("我们在一起吧!💖");
  } else {
    失败("你是个好人😭");
  }
})
.then(result => console.log(result))
.catch(error => console.log(error));

正确的理解方式

// JavaScript 引擎说:"我给你两个函数,你用它们来告诉我结果"
new Promise((引擎给的resolve函数, 引擎给的reject函数) => {
  
  // 我们的业务逻辑
  const 表白成功 = true;
  
  if (表白成功) {
    引擎给的resolve函数("💖 接受表白"); // 告诉引擎:成功了
  } else {
    引擎给的reject函数("😭 拒绝表白");  // 告诉引擎:失败了
  }
});

更清晰的比喻

想象你去餐厅点餐:

// 餐厅(JavaScript引擎)给你一个呼叫器
const 点餐Promise = new Promise((上菜, 退款) => {
  // 厨房处理中...
  setTimeout(() => {
    if (有食材) {
      上菜("您的菜品"); // 按下"上菜"按钮
    } else {
      退款("食材不足"); // 按下"退款"按钮
    }
  }, 3000);
});

// 你等待结果
点餐Promise
  .then(菜品 => console.log("收到:", 菜品))
  .catch(原因 => console.log("退款原因:", 原因));

总结

  • resolvereject 不是我们定义的
  • 它们是 JavaScript 引擎创建并传递给我们的
  • 我们只是 使用 这两个函数来告诉 Promise 最终的结果
  • 参数名可以任意,但约定俗成叫 resolvereject

所以你不是在定义函数,而是在 使用 JavaScript 提供给你的通信工具


评论