在非 async 函数中调用 async 函数:从 .then 到 async/await 的完整实战解析(zh.javascript.info)
文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载导读在《现代 JavaScript 教程》zh.javascript.info的 async/await 章节中在非 async 函数中调用 async 函数是一道高频出现的经典题目当你在一个普通函数非 async内部需要拿到async函数的返回值时await在语法上不可用这时应该怎么写本文将围绕该任务task.md及其官方解法solution.md从底层原理async 函数本质上是返回 Promise 的包装讲起给出可直接复制运行的解决方案、常见误区、错误处理策略并结合仓库中的同类练习帮助你彻底掌握普通函数 ↔ async 函数之间的协作方式。一、任务背景为什么在普通函数里拿不到 async 的返回值题目给出的代码如下async function wait() { await new Promise(resolve setTimeout(resolve, 1000)); return 10; } function f() { // ……这里你应该怎么写 // 我们需要调用 async wait() 并等待以拿到结果 10 // 记住我们不能使用 await }任务的核心约束是在f中不能使用await。原因在 article.md 中有明确警告——await只能在async函数内使用function f() { let promise Promise.resolve(1); let result await promise; // Syntax errorawait 只在 async 函数中有效 }在非 async 函数中直接写await会直接触发SyntaxError语法错误。所以问题的本质是如何在一个无法使用await的普通函数中获取一个 Promise 的最终结果。二、原理剖析async 函数本质上是返回 Promise 的函数要回答这个问题必须先理解async关键字的作用。根据 article.md 的说明async放在函数前面表达了一件简单的事这个函数总是返回一个 Promise允许在该函数内使用await。其他返回值会被自动包装进一个 resolved 的 promise 中。例如async function f() { return 1; } f().then(alert); // 1即使显式返回Promise.resolve(1)结果也完全一样async function f() { return Promise.resolve(1); } f().then(alert); // 1换句话说async函数对于外部调用者来说就是一个返回 Promise 的普通函数。async/await只是 Promise 之上的一层更舒适的语法糖这是 article.md 开篇的原话底层机制始终是 Promise。这一结论直接决定了答案的方向既然wait()返回的是 Promise那么在普通函数f中我们完全可以把它当作一个普通 Promise 来处理。三、标准解法把 async 调用当作 Promise接上 .then官方解法solution.md非常直接——把 async 调用当作 promise 对待并在它后面加上.thenasync function wait() { await new Promise(resolve setTimeout(resolve, 1000)); return 10; } function f() { // 1 秒后显示 10 wait().then(result alert(result)); } f();运行后 1 秒页面会弹出10。这段代码的完整执行链如下f()被调用它是一个普通非 async函数内部没有awaitwait()被调用返回一个 Promise该 Promise 在 1 秒后以值10resolvewait().then(result alert(result))注册了回调Promise settle 后回调执行result即为10f()本身立即返回它不等待异步结果由回调最终呈现。这正是async/await与 Promise 协作的典型模式在 async 函数内部用await拿结果在普通函数尤其是顶层作用域用.then/.catch拿结果。四、为什么不能直接等待await 的作用域限制也许你会想能不能让f也变成 async可以但那就不再是普通函数中调用这一场景了。本题刻意要求f保持普通函数身份原因在于await会暂停所在函数的执行直到 promise settlearticle.md 明确说明这一暂停不消耗 CPU因为引擎可同时处理其他任务这种暂停并继续的能力只能存在于async函数内部在普通函数中f一旦返回调用方拿到的只是undefined异步结果必须通过回调链.then或由f返回的 Promise 传递出去。因此普通函数与 async 结果交互的唯一通道就是Promise 链。这也是 article.md 中async/await和promise.then/catch一节的结论当我们在代码顶层所有 async 函数之外时语法上无法使用await通常的做法就是添加.then/.catch来处理最终结果或掉出来的 error。变体一让 f 也返回 Promise如果调用方还需要继续链式处理可以让f把 Promise 传出去function f() { return wait().then(result { alert(result); return result; }); } f().then(result console.log(最终拿到, result));此时f不再返回undefined而是返回一个 Promise调用方可以继续.then或.catch。这在需要把异步结果一路传递到更外层的场景中非常常见。变体二顶层作用域用 async IIFE 包装如果问题是在顶层代码中等待 async 结果article.md 给出了两种合法途径现代浏览器在module模块中允许顶层await非 module 场景下把代码包进匿名的 async 立即执行函数IIFE(async () { let response await fetch(/article/promise-chaining/user.json); let user await response.json(); // ... })();这两种方式与本题的.then解法互为补充.then用于普通函数内async IIFE 用于顶层立即执行。五、错误处理在普通函数里捕获 async 的 rejection在普通函数中使用.then时别忘了处理失败分支。async函数内部如果await的 promise 被 reject该函数返回的 Promise 会变成 rejected就像在函数里throw了 error 一样见 article.md 的Error 处理一节。所以上面的代码应当补上.catchfunction f() { wait() .then(result alert(result)) .catch(err console.error(wait() 失败, err)); }如果忘记.catch就会得到未处理的 promise rejection可在控制台查看甚至可以用全局的unhandledrejection事件来兜底。这一点在 article.md 中同样有明确提醒当我们忘了添加.catch时就会得到一个未处理的 promise error。六、仓库同类练习async/await 与 .then/.catch 的互转普通函数中拿 async 结果这一模式在仓库同一章节的练习中被反复运用可交叉印证6.1 用 async/await 重写 loadJson01-rewrite-async把基于.then的loadJson改写为async函数后调用方顶层非 async依然要用.catch接住错误async function loadJson(url) { let response await fetch(url); if (response.status 200) { let json await response.json(); return json; } throw new Error(response.status); } loadJson(https://javascript.info/no-such-user.json) .catch(alert); // Error: 404官方解法01-rewrite-async/solution.md特别指出loadJson抛出的 error 由.catch处理这里不能使用await loadJson(…)因为我们不在 async 函数中——这正是本篇文章核心结论的又一次实战体现。6.2 用 async/await 重写 rethrow02-rewrite-async-2改写后的demoGithubUser在while(true)循环中await loadJson(...)并用try..catch捕获HttpError404 时提示重新输入未知错误则 rethrow见 02-rewrite-async-2/solution.md。它展示了在 async 函数内部用 await try/catch、在 async 函数外部用 .catch两种写法的分工。这两个练习与本题03-async-from-regular共同构成一个完整的能力闭环场景推荐写法依据async 函数内部获取结果let v await promisetry..catcharticle.md普通函数/顶层获取结果promise.then/.catchsolution.md顶层批量等待多个 promiseawait Promise.all([...])配合 async IIFEarticle.md顶层module 内直接等待顶层awaitarticle.md七、总结普通函数调用 async 函数的三条铁律记住身份async函数对调用者而言就是一个返回 Promise 的函数article.md 开篇定义这是所有解法的出发点普通函数内用.then/.catch不能写await用 Promise 链拿结果与接错误solution.md需要继续传递时返回 Promise让普通函数return promise把异步结果一路向上游传递调用方继续链式处理或交给 async 调用者await。这道题虽然简单却是 async/await 新手最容易卡住的点——理解了async 函数 Promise 工厂之后普通函数与 async 代码之间的协作就会变得非常自然。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐现代 JavaScript 教程在普通非 async函数中调用 async 函数把 async 调用当作 Promise 处理现代 JavaScript 教程在普通非 async函数中调用 async 函数把 async 调用当作 Promise 处理 await 只能在 as文档教程前端在普通函数中安全调用 async 函数Modern JavaScript Tutorial 的 .then 解决方案在普通函数中安全调用 async 函数Modern JavaScript Tutorial 的 .then 解决方案 在 JavaScript 中 asyn文档/教程前端从普通函数调用 async 函数JavaScript 非异步上下文中的 Promise 桥接实战从普通函数调用 async 函数JavaScript 非异步上下文中的 Promise 桥接实战 async 函数内部可以使用 await 优雅地等待 Pro文档/教程前端上一篇Angular SVG圆形进度条无障碍访问确保WCAG 2.1合规性指南下一篇Rax框架版本迁移工具自动化升级的实用脚本与工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考