从Promise链到Async Function:azu/promises-book中的异步编程演进

从Promise链到Async Function:azu/promises-book中的异步编程演进

引言

在现代JavaScript开发中,异步编程是每个开发者必须掌握的核心技能。azu/promises-book项目深入探讨了Promise和Async Function的使用方法,为我们展示了从传统Promise链到现代Async/Await的演进过程。本文将重点分析如何将基于Promise的链式调用转换为更简洁的Async Function写法。

模拟异步请求:dummyFetch函数

在讲解转换过程前,我们先建立一个基础的异步请求模拟函数:

function dummyFetch(path) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (path.startsWith("/resource")) {
                resolve({ body: `Response body of ${path}` });
            } else {
                reject(new Error("NOT FOUND"));
            }
        }, 1000 * Math.random());
    });
}

这个dummyFetch函数模拟了网络请求的异步特性:

  • 随机延迟(1000ms内)
  • 成功路径(以/resource开头的路径)
  • 失败路径(其他路径)

Promise链式调用处理数组资源

传统上,我们使用Promise链来处理多个异步请求:

function fetchResources(resources) {
    const results = [];
    return resources.reduce((promise, resource) => {
        return promise.then(() => {
            return dummyFetch(resource).then((response) => {
                results.push(response.body);
                return results;
            });
        });
    }, Promise.resolve());
}

这种写法虽然可行,但存在几个问题:

  1. 嵌套层级深,可读性差
  2. 错误处理复杂
  3. 需要手动维护结果数组

使用Async Function重构

使用Async/Await语法可以大幅简化代码:

async function fetchResources(resources) {
    const results = [];
    for (const resource of resources) {
        const response = await dummyFetch(resource);
        results.push(response.body);
    }
    return results;
}

改进点:

  1. 代码呈现线性结构,更易理解
  2. 错误处理可以使用try/catch
  3. 不需要手动管理Promise链

常见陷阱与注意事项

1. forEach中的await陷阱

许多开发者会尝试在forEach中使用await:

// 错误示例
async function fetchResources(resources) {
    const results = [];
    resources.forEach(async (resource) => {
        const response = await dummyFetch(resource);
        results.push(response.body);
    });
    return results;  // 总是返回空数组
}

问题原因:

  • forEach不会等待异步回调完成
  • 函数会在所有await完成前返回

解决方案:

  • 使用for...of循环
  • 或使用map+Promise.all

2. await的作用域限制

await只能在Async Function内部使用:

function main() {
    await Promise.resolve(); // SyntaxError
}

这是JavaScript的故意设计,防止误用导致整个程序阻塞。

进阶技巧:结合Promise.all优化

当请求顺序不重要时,可以使用Promise.all并行处理:

async function fetchResources(resources) {
    const responses = await Promise.all(
        resources.map(resource => dummyFetch(resource))
    );
    return responses.map(response => response.body);
}

优势:

  1. 所有请求并行发送
  2. 总时间取决于最慢的请求
  3. 代码更简洁

性能考量

选择串行还是并行取决于业务需求:

  1. 串行请求(for...of + await):

    • 保证执行顺序
    • 适用于有依赖关系的请求
    • 总时间 = 各请求时间之和
  2. 并行请求(Promise.all):

    • 最大化利用网络带宽
    • 适用于独立请求
    • 总时间 ≈ 最慢的请求时间

错误处理比较

Promise链和Async/Await的错误处理方式对比:

// Promise链
fetchResources(resources)
    .then(handleSuccess)
    .catch(handleError);

// Async/Await
try {
    const results = await fetchResources(resources);
    handleSuccess(results);
} catch (error) {
    handleError(error);
}

Async/Await的优势在于可以将异步错误用同步方式处理。

结论

azu/promises-book项目展示了JavaScript异步编程的演进:

  1. 从回调地狱到Promise链
  2. 从Promise链到Async/Await
  3. 结合Promise API与Async/Await的最佳实践

关键要点:

  • Async/Await提供了更直观的异步代码写法
  • 理解底层Promise机制仍然重要
  • 根据场景选择合适的处理模式(串行/并行)
  • 注意常见陷阱,特别是循环中的await使用

通过掌握这些技术,开发者可以编写出既简洁又高效的异步JavaScript代码。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宁雨澄Alina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值