强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

异步与协程精讲 / 第4章:Promise —— 异步的承诺

第4章:Promise —— 异步的承诺

4.1 什么是 Promise?

Promise(承诺)是对一个异步操作最终完成或失败的对象表示。它不是回调的替代品,而是对回调模式的抽象升级

类比:Promise 就像一张外卖订单——你下单后得到一个订单号(Promise 对象),你可以随时查看订单状态,订单完成后你会收到食物(结果),或者被告知配送失败(错误)。

三种状态

                then()
 Pending ─────────────► Fulfilled (已完成)
   │                      │  value
   │                      │
   │     catch()          │
   └───────────────► Rejected (已拒绝)
                        reason
状态 英文 说明 能否转换
待定 Pending 初始状态,既未完成也未拒绝 → Fulfilled 或 Rejected
已完成 Fulfilled 操作成功完成,有结果值 不可再变
已拒绝 Rejected 操作失败,有错误原因 不可再变

关键规则:状态一旦改变,就不可逆(immutable)。一个 Promise 只能从 Pending 变为 Fulfilled 或 Rejected,之后不会再改变。


4.2 创建 Promise

JavaScript

// 基本创建
const promise = new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            resolve('操作成功');    // → Fulfilled
        } else {
            reject(new Error('操作失败'));  // → Rejected
        }
    }, 1000);
});

// 使用
promise
    .then(value => console.log(value))
    .catch(err => console.error(err.message));

包装已有回调

// 将 Node.js 回调风格转为 Promise
const { promisify } = require('util');
const fs = require('fs');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

// 手动包装
function readFileManual(path, encoding) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, encoding, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

// Node.js 16+ 内置的 promise API
const fsPromises = require('fs/promises');
await fsPromises.readFile('/path', 'utf8');

Python

import asyncio

async def fetch_data():
    await asyncio.sleep(1)  # 模拟 I/O
    return {'status': 'ok', 'data': [1, 2, 3]}

# Python 的协程本身就是类似 Promise 的概念
result = asyncio.run(fetch_data())

Java

// CompletableFuture 是 Java 的 Promise 实现
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步操作
    return fetchDataFromDB();
});

future
    .thenApply(data -> processData(data))      // 转换
    .thenAccept(result -> saveResult(result))   // 消费
    .exceptionally(err -> {                     // 错误处理
        log.error("Failed:", err);
        return null;
    });

Rust

use tokio;

#[tokio::main]
async fn main() {
    let result = fetch_data().await;
    println!("结果: {:?}", result);
}

async fn fetch_data() -> Result<String, reqwest::Error> {
    let resp = reqwest::get("https://api.example.com").await?;
    let body = resp.text().await?;
    Ok(body)
}

4.3 链式调用(Chaining)

链式调用是 Promise 最强大的特性之一。每次 .then() 都会返回一个新的 Promise,从而形成调用链。

链式调用的规则

返回类型 新 Promise 的状态 新 Promise 的值
普通值 Fulfilled 该值
新 Promise 由该 Promise 决定 由该 Promise 决定
抛出异常 Rejected 该异常
// ❌ 回调地狱
getUser(userId, (err, user) => {
    getProduct(productId, (err, product) => {
        createOrder(user, product, (err, order) => {
            sendNotification(user.email, order, (err) => {
                console.log('完成');
            });
        });
    });
});

// ✅ Promise 链式调用
getUser(userId)
    .then(user => getProduct(productId).then(product => ({ user, product })))
    .then(({ user, product }) => createOrder(user, product))
    .then(order => sendNotification(user.email, order))
    .then(() => console.log('完成'))
    .catch(err => console.error('失败:', err));

扁平化技巧

// 使用 async/await 是最直接的方式(第 5 章详述)
async function placeOrder(userId, productId) {
    const user = await getUser(userId);
    const product = await getProduct(productId);
    const order = await createOrder(user, product);
    await sendNotification(user.email, order);
    return order;
}

4.4 错误传播

Promise 的错误传播机制是它相比回调最大的优势之一。

错误冒泡

Promise.resolve('start')
    .then(value => {
        throw new Error('步骤 1 失败');
    })
    .then(value => {
        console.log('这不会执行');
    })
    .then(value => {
        console.log('这也不会执行');
    })
    .catch(err => {
        // 错误会一直冒泡,直到被某个 catch 捕获
        console.error('捕获到:', err.message);  // "步骤 1 失败"
    });

catch vs then 的第二个参数

// 方式一:使用 .catch()
promise
    .then(onFulfilled)
    .catch(onRejected);  // 捕获前面所有步骤的错误

// 方式二:使用 .then() 的第二个参数
promise.then(onFulfilled, onRejected);  // 只捕获 promise 本身的错误

// 区别:catch 能捕获 onFulfilled 中抛出的错误
promise
    .then(value => {
        throw new Error('then 中的错误');
    })
    .catch(err => {
        console.error('能捕获:', err);  // ✅
    });

promise.then(
    value => { throw new Error('then 中的错误'); },
    err => {
        console.error('不能捕获:', err);  // ❌ 只捕获 promise 本身的错误
    }
);

最佳实践:始终使用 .catch() 而非 .then() 的第二个参数,除非你有明确的理由。

未处理的拒绝(Unhandled Rejection)

// ⚠️ 忘记处理 rejection 会导致 unhandledRejection
const bad = Promise.reject(new Error('没有人处理我'));

// 正确做法:总是以 .catch() 结尾
const good = Promise.reject(new Error('我会被处理'))
    .catch(err => console.error('已处理:', err.message));

// Node.js 15+ 默认将 unhandledRejection 视为致命错误
process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的拒绝:', reason);
    // 建议记录日志后优雅退出
    process.exit(1);
});

4.5 并发组合方法

Promise 提供了多个静态方法来处理并发异步操作。

Promise.all — 全部成功

// 等待所有 Promise 都成功,任一失败则立即失败
const results = await Promise.all([
    fetch('/api/users'),
    fetch('/api/products'),
    fetch('/api/orders'),
]);
特性 说明
成功条件 所有 Promise 都 Fulfilled
失败条件 任一 Promise Rejected
返回值 所有结果的数组,顺序与输入一致
短路行为 任一失败立即返回,不等待其余

Promise.allSettled — 全部完成(ES2020)

// 等待所有 Promise 都结束(无论成功或失败)
const results = await Promise.allSettled([
    fetch('/api/users'),
    fetch('/api/products'),
    fetch('/api/orders'),
]);

// 每个结果对象:
// { status: 'fulfilled', value: ... }
// { status: 'rejected', reason: ... }
特性 说明
成功条件 永远不会 reject(总是等待所有完成)
返回值 每个 Promise 的结果状态和值
适用场景 需要所有结果,不因单个失败而中断

Promise.race — 竞速

// 返回第一个完成的 Promise(无论成功或失败)
const fastest = await Promise.race([
    fetch('/api/fast-server'),
    fetch('/api/slow-server'),
    new Promise((_, reject) =>
        setTimeout(() => reject(new Error('超时')), 5000)
    ),
]);
特性 说明
返回条件 第一个 Fulfilled 或 Rejected 的 Promise
常见用途 超时控制、多源竞速

Promise.any — 任一成功(ES2021)

// 返回第一个成功的 Promise,全部失败才拒绝
const result = await Promise.any([
    fetch('/api/server-1'),
    fetch('/api/server-2'),
    fetch('/api/server-3'),
]);

// 如果全部失败,抛出 AggregateError
try {
    const result = await Promise.any(failingPromises);
} catch (err) {
    console.error(err.errors);  // 所有错误的数组
}

组合方法对比

方法 成功条件 失败条件 返回值 等价类比
all 全部成功 任一失败 结果数组 AND
allSettled 永不 reject 永不 reject 状态数组 等待
race 任一完成 任一完成 第一个结果 OR
any 任一成功 全部失败 第一个成功值 OR(忽略错误)

4.6 Promise.finally

finally() 在 Promise 结束后执行,无论成功或失败。

function fetchData(url) {
    showLoadingSpinner();
    return fetch(url)
        .then(res => res.json())
        .catch(err => {
            showErrorNotification(err);
            throw err;  // 重新抛出,让调用方也知道失败了
        })
        .finally(() => {
            hideLoadingSpinner();  // 无论成功失败都隐藏加载动画
        });
}

finally 的特性

const p = Promise.resolve(42);

p.finally(() => {
    console.log('finally 执行');
    // 1. 没有返回值 → 原始值 42 传递给下一个 then
    // 2. 返回一个普通值 → 同上,值被忽略
    // 3. 返回一个 Promise → 等待它完成,但值仍被忽略
    // 4. 抛出异常 → 新的 Rejected Promise
}).then(value => {
    console.log(value);  // 42
});

4.7 实用工具函数

超时控制

function withTimeout(promise, ms) {
    return Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error(`操作超时 (${ms}ms)`)), ms)
        ),
    ]);
}

// 使用
try {
    const data = await withTimeout(fetch('/api/slow'), 3000);
} catch (err) {
    console.error(err.message);  // "操作超时 (3000ms)"
}

重试

async function withRetry(fn, retries = 3, delay = 1000) {
    for (let i = 0; i < retries; i++) {
        try {
            return await fn();
        } catch (err) {
            if (i === retries - 1) throw err;
            await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
        }
    }
}

// 使用
const data = await withRetry(() => fetch('/api/unreliable'), 3, 1000);

限流(并发控制)

async function parallelLimit(tasks, limit) {
    const results = [];
    const executing = new Set();

    for (const [i, task] of tasks.entries()) {
        const p = task().then(result => {
            executing.delete(p);
            return result;
        });
        executing.add(p);
        results[i] = p;

        if (executing.size >= limit) {
            await Promise.race(executing);
        }
    }

    return Promise.all(results);
}

// 使用:最多同时 3 个请求
const tasks = urls.map(url => () => fetch(url));
const results = await parallelLimit(tasks, 3);

4.8 业务场景:API 网关聚合

场景

前端一个页面需要同时调用 5 个后端 API 获取数据,需要:

  1. 并发请求所有 API(加速)
  2. 单个 API 超时不阻塞整体
  3. 部分失败时返回已有数据
async function aggregateAPIs(requests) {
    const results = {};

    const settled = await Promise.allSettled(
        requests.map(async ({ key, url, timeout = 3000 }) => {
            const response = await withTimeout(fetch(url), timeout);
            const data = await response.json();
            return { key, data };
        })
    );

    for (const result of settled) {
        if (result.status === 'fulfilled') {
            results[result.value.key] = result.value.data;
        } else {
            console.warn(`API 失败:`, result.reason);
        }
    }

    return results;
}

// 使用
const data = await aggregateAPIs([
    { key: 'user',    url: '/api/user/123' },
    { key: 'orders',  url: '/api/orders?user=123' },
    { key: 'profile', url: '/api/profile/123' },
    { key: 'settings',url: '/api/settings/123' },
    { key: 'notifications', url: '/api/notifications/123', timeout: 5000 },
]);

4.9 各语言 Promise 对比

语言 类名 创建 组合方法
JavaScript Promise new Promise() all, allSettled, race, any
Python asyncio.Future loop.create_future() asyncio.gather, asyncio.wait
Java CompletableFuture supplyAsync() allOf, anyOf, thenCombine
Rust Future (trait) async {} join!, select!, try_join!
C++ std::future std::async() 无内置,需第三方库
Go 无内置(使用 Channel) make(chan T) select

4.10 本章小结

要点 说明
Promise 状态 Pending → Fulfilled / Rejected,不可逆
链式调用 .then() 返回新 Promise,实现扁平化
错误传播 错误会冒泡,直到被 .catch() 捕获
并发组合 all(AND)、race(竞速)、any(OR)、allSettled(等待)
finally 无论成功失败都执行,适合清理操作
工具函数 超时、重试、限流是常见需求

下一章预告:Promise 虽然比回调好得多,但链式调用仍然不够直观。async/await 让异步代码看起来像同步代码——这是异步编程的"终极语法糖"。


扩展阅读