ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

现代 JavaScript 教程:深入理解 async/await —— 从语法糖到 Promise 底层原理

现代 JavaScript 教程:深入理解 async/await —— 从语法糖到 Promise 底层原理 文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载async/await 是 JavaScript 中基于 Promise 的一层特殊语法它让异步代码的编写与阅读方式无限接近同步代码从而显著降低回调嵌套callback hell与 Promise 链式调用的心智负担。本教程以《现代 JavaScript 教程》The Modern JavaScript Tutorial本仓库 1-js/11-async/08-async-await/article.md为骨架结合仓库内配套练习01-rewrite-async、02-rewrite-async-2、03-async-from-regular与 Promise 链章节源码带你从零掌握async关键字的语义、await的暂停与恢复机制、错误处理三板斧、顶层 await 以及Promise.all并行等待等核心能力。读完本章你将能够把任意基于.then/.catch的异步代码改写成更优雅的async/await形式并准确理解它背后的 Promise 行为。一、async函数总是返回 Promiseasync关键字可以被放置在任意函数包括函数声明、函数表达式、箭头函数前面。它表达了一件非常简单的事情这个函数总是返回一个 promise。async function f() { return 1; }上面这个函数返回的是结果为1的 resolved promise。函数体内返回的其他非 promise 值会被自动包装进一个 resolved 的 promise 中。你可以直接在调用后链式.then来验证async function f() { return 1; } f().then(alert); // 1当然你也可以显式地返回一个 promise结果完全一致async function f() { return Promise.resolve(1); } f().then(alert); // 1所以async确保了函数返回值一定是 promise并把非 promise 值自动包装进去。但它的作用不止于此——只有async函数内部才能合法使用await这是另一个同样关键的关键字。二、await优雅地等待 Promise 完成await的语法只有一行且只在 async 函数内工作// 只在 async 函数内工作 let value await promise;关键字await会让 JavaScript 引擎等待直到该 promise 完成settle即 resolve 或 reject然后返回其结果。看一个 1 秒后 resolve 的示例async function f() { let promise new Promise((resolve, reject) { setTimeout(() resolve(done!), 1000) }); let result await promise; // 等待直到 promise resolve (*) alert(result); // done! } f();函数执行时会在(*)那一行“暂停”等到 promise settle 后拿到result继续往下执行因此 1 秒后弹出done!。需要特别强调的是await暂停函数执行不会消耗任何 CPU 资源。JavaScript 引擎在等待期间可以同时处理其他任务——执行其他脚本、处理事件等。这与阻塞式同步等待有本质区别。从本质上看await promise只是获取 promise 结果的更优雅语法和promise.then做的事情相同但更易于读写。2.1 不能在没有async的普通函数中使用await如果你在非 async 函数中使用await会直接得到语法错误function f() { let promise Promise.resolve(1); let result await promise; // Syntax error }这个错误最常见的来源就是忘记在函数前面写async。记住await只在async函数中有效。2.2 实战改写用 async/await 重写 Promise 链的 showAvatar 示例本仓库 03-promise-chaining/article.md 一章给出了用.then链实现的showAvatar()示例依次读取本地 JSON、请求 GitHub 用户信息、显示头像、3 秒后移除。用async/await改写只需两步用await替换掉所有.then调用在函数前面加上async关键字。改写后的版本直观清晰async function showAvatar() { // 读取我们的 JSON let response await fetch(/article/promise-chaining/user.json); let user await response.json(); // 读取 github 用户信息 let githubResponse await fetch(https://api.github.com/users/${user.name}); let githubUser await githubResponse.json(); // 显示头像 let img document.createElement(img); img.src githubUser.avatar_url; img.className promise-avatar-example; document.body.append(img); // 等待 3 秒 await new Promise((resolve, reject) setTimeout(resolve, 3000)); img.remove(); return githubUser; } showAvatar();对比原 Promise 链中为了在头像移除后再执行后续动作而不得不手工return new Promise(...)并调用resolve的写法async/await版本把异步流程彻底“拉平”了每一步都按顺序直观书写。三、三个进阶场景3.1 顶层 await现代浏览器 Module 中允许在现代浏览器中当代码位于 module 中时在顶层直接使用await也是被允许的模块的顶层本质上被当作 async 上下文处理。Module 的详细机制见本仓库 13-modules/01-modules-intro/article.md 一章。// 我们假设此代码在 module 中的顶层运行 let response await fetch(/article/promise-chaining/user.json); let user await response.json(); console.log(user);如果没有使用 modules或者必须兼容旧版本浏览器那么通用的替代方案是把顶层代码包装进一个匿名的 async 函数IIFE中(async () { let response await fetch(/article/promise-chaining/user.json); let user await response.json(); ... })();3.2await接受 thenables与promise.then一样await也允许使用 thenable 对象——即那些具有可调用then方法的对象。第三方对象可能不是一个真正的 promise但只要它实现了.then方法就是 promise 兼容的可以对其使用await。下面的Thenable类实例就被await正常接受了class Thenable { constructor(num) { this.num num; } then(resolve, reject) { alert(resolve); // 1000ms 后使用 this.num*2 进行 resolve setTimeout(() resolve(this.num * 2), 1000); // (*) } } async function f() { // 等待 1 秒之后 result 变为 2 let result await new Thenable(1); alert(result); } f();当await接收一个非 promise 但带.then方法的对象时它会调用该.then方法并把内建的resolve和reject函数作为参数传入就像对待一个常规的Promiseexecutor 那样。随后await等待直到其中某个函数被调用上例发生在(*)行再用得到的结果继续执行。这个特性允许自定义对象与 Promise 机制无缝集成而不必继承自Promise。3.3 Class 中的 async 方法要声明一个 class 中的 async 方法只需在对应方法前面加上asyncclass Waiter { async wait() { return await Promise.resolve(1); } } new Waiter() .wait() .then(alert); // 1alert 等同于 result alert(result)含义与普通 async 函数一致确保该方法返回一个 promise并且可以在方法体内使用await。四、错误处理await 把 reject 变成 throw如果 promise 正常 resolveawait promise返回其结果但如果 promise 被 rejectawait会抛出throw这个 error就像在该行写了一个throw语句那样。因此下面两段代码完全等价async function f() { await Promise.reject(new Error(Whoops!)); }async function f() { throw new Error(Whoops!); }在真实开发中promise 可能需要一段时间后才 reject这种情况下await抛出 error 之前会有延时。4.1 用 try..catch 捕获由于 reject 变成了 throw我们就可以用常规的try..catch来捕获方式和捕获同步throw完全相同async function f() { try { let response await fetch(http://no-such-url); } catch(err) { alert(err); // TypeError: failed to fetch } } f();一旦有 error 发生执行控制权立即移交至catch块。try也可以包裹多行await代码async function f() { try { let response await fetch(/no-user-here); let user await response.json(); } catch(err) { // 捕获到 fetch 和 response.json 中的错误 alert(err); } } f();4.2 没有 try..catch 时函数调用返回 rejected promise如果没有try..catch由 async 函数调用生成的 promise 将变成 rejected。此时可以在调用处追加.catch来处理async function f() { let response await fetch(http://no-such-url); } // f() 变成了一个 rejected 的 promise f().catch(alert); // TypeError: failed to fetch // (*)如果连.catch都忘了加就会得到一个未处理的 promise error可在控制台查看。此时可以用 04-promise-error-handling/article.md 一章讲到的全局事件处理程序unhandledrejection来兜底捕获这类 error。4.3async/await与promise.then/catch的取舍使用async/await时几乎不会再写.then——await已经替我们处理了等待错误处理也改用常规try..catch而非.catch这通常但不总是更加方便。但有一个例外必须牢记在代码顶层、所有async函数之外语法上不能使用await此时通常的做法是添加.then/catch来处理最终结果或掉出来的 error正如上面(*)行所示。五、async/await与Promise.all并行等待多个 Promise当需要同时等待多个 promise 时而不是依次等待用Promise.all把它们包装起来再await// 等待结果数组 let results await Promise.all([ fetch(url1), fetch(url2), ... ]);如果其中出现 error它会正常传递从失败的那个 promise 传到Promise.all再变成我们能通过try..catch在调用周围捕获到的异常。这正是async/await与并行任务组合的标准姿势——顺序依赖用await逐个等待互不依赖的任务用Promise.all并发执行。六、配套练习从 .then 链到 async/await 的完整迁移实战仓库为本章配了三道递进式练习可以很好地检验你对本章知识的掌握程度。6.1 练习一重写 loadJson任务01-rewrite-async/task.md是把来自 Promise 链章节的loadJson用async/await替代.then/catch重写。原代码function loadJson(url) { return fetch(url) .then(response { if (response.status 200) { return response.json(); } else { throw new Error(response.status); } }); } loadJson(https://javascript.info/no-such-user.json) .catch(alert); // Error: 404参考答案01-rewrite-async/solution.mdasync function loadJson(url) { // (1) let response await fetch(url); // (2) if (response.status 200) { let json await response.json(); // (3) return json; } throw new Error(response.status); } loadJson(https://javascript.info/no-such-user.json) .catch(alert); // Error: 404 (4)关键解析将函数loadJson声明为async将函数体内所有.then替换为await其实return response.json()也可以不等待直接返回让外部代码await这个 promise resolve本例中两种写法结果相同loadJson抛出的 error 由调用处的.catch处理——因为我们不在 async 函数中无法使用await loadJson(...)。6.2 练习二用 async/await 重写 rethrow 并用循环取代递归任务02-rewrite-async-2/task.md是把 rethrow重抛示例改写为async/await并且利用循环摆脱原来的递归写法。原代码通过自定义HttpError类、loadJson与递归的demoGithubUser实现“反复询问用户名直到 GitHub 返回合法用户”的逻辑。参考答案02-rewrite-async-2/solution.md的核心思路把demoGithubUser中的.catch替换为try...catch并在需要处加上async/awaitclass HttpError extends Error { constructor(response) { super(${response.status} for ${response.url}); this.name HttpError; this.response response; } } async function loadJson(url) { let response await fetch(url); if (response.status 200) { return response.json(); } else { throw new HttpError(response); } } // 询问用户名直到 github 返回一个合法的用户 async function demoGithubUser() { let user; while(true) { let name prompt(Enter a name?, iliakan); try { user await loadJson(https://api.github.com/users/${name}); break; // 没有 error退出循环 } catch(err) { if (err instanceof HttpError err.response.status 404) { // 循环将在 alert 后继续 alert(No such user, please reenter.); } else { // 未知的 error再次抛出rethrow throw err; } } } alert(Full name: ${user.name}.); return user; } demoGithubUser();这里的while(true)try..catch循环优雅地替代了原来.catch里递归调用demoGithubUser()的写法——这正是async/await让异步代码获得同步式控制流循环、分支能力的典型体现。6.3 练习三在普通函数中调用 async 函数任务03-async-from-regular/task.md有一个普通函数f如何在其中调用 async 函数wait()并拿到结果 10记住普通函数内不能使用await。参考答案03-async-from-regular/solution.md非常简单——把 async 调用当作 promise 对待在后面追加.then即可async function wait() { await new Promise(resolve setTimeout(resolve, 1000)); return 10; } function f() { // 1 秒后显示 10 wait().then(result alert(result)); } f();这个练习的答案一句话就能说清但它是 async/await 新手最常见困惑之一async 函数从内部看是同步式写法从外部看依然是返回 promise 的函数。只要理解了这一层就知道跨出 async 边界时.then仍是必备工具。七、总结函数前面的关键字async有两个作用让这个函数总是返回一个 promise允许在该函数内使用await。Promise 前的关键字await使 JavaScript 引擎等待该 promise settle然后如果有 error就会抛出异常——就像那里调用了throw error一样否则就返回结果。这两个关键字共同提供了一套易于阅读、易于编写的异步代码框架。有了async/await之后我们几乎不再需要promise.then/catch但请不要忘记它们本质上仍是基于 promise 的——有些场景例如最外层作用域、从普通函数调用 async 函数不得不使用.then/catch而当需要同时等待多个任务时Promise.all依然是并行利器。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐Jekyll Compose与编辑器集成配置Atom、VSCode等编辑器自动打开Jekyll Compose与编辑器集成配置Atom、VSCode等编辑器自动打开 Jekyll Compose是一个强大的Jekyll插件专门为内容创作者开发工具3个核心模块如何构建企业级语音交互系统从零到一的技术实现指南3个核心模块如何构建企业级语音交互系统从零到一的技术实现指南 在数字化浪潮席卷各行各业的今天语音交互系统已成为企业客户服务、智能助手和自动化流程的关键入口。人工智能AI Agent语音多模态后端WebRTC交互助手如何优化Qwen3.6-27B-Uncensored-HauhauCS-Balanced推理性能温度、top_p等参数设置技巧如何优化Qwen3.6 27B Uncensored HauhauCS Balanced推理性能温度、top_p等参数设置技巧 Qwen3.6 27B Unc上一篇web3-providers-ws 4.x 演进全解从 changelog 看 WebSocket 提供者的架构重构、构建体系与稳定化之路下一篇Ray Tune Callback 与 Metrics 完全指南自定义回调、日志指标与自动填充字段解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表