强曰为道

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

第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 对比

语言类名创建组合方法
JavaScriptPromisenew Promise()all, allSettled, race, any
Pythonasyncio.Futureloop.create_future()asyncio.gather, asyncio.wait
JavaCompletableFuturesupplyAsync()allOf, anyOf, thenCombine
RustFuture (trait)async {}join!, select!, try_join!
C++std::futurestd::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 让异步代码看起来像同步代码——这是异步编程的"终极语法糖"。


扩展阅读