JS 异步
前情提要
浏览器 API 中,有些功能强大、但很耗时的函数。
这种耗时长的同步函数,运行时会有很大问题:运行的时候,整个程序会卡着,怎么点、怎么动都没反应!
用户什么都做不了,肯定会不耐烦。得想办法解决。
你可以想到一个基本思路:
- 耗时长的函数 “在后台继续运行”;
- 同时,用户 / 程序本身可以继续执行比较简单的同步操作。
异步
- mdn 概念:
异步(Asynchronous)指的是两个或多个对象或事件不同时存在或发生,也就是说,它们不是同步的。 当多个相关的,但是并不依赖于前面发生的事情完成的事情发生时,它们就是异步的。
个人理解:多个相关事件不同时发生、且并没有依次发生,且事件的发生顺序不依赖于事件代码的排序先后。
异步特征:
-
在等待响应时不会阻塞其他进程;
我记得我以前拿上🚽做例子,来记忆异步和 Promise 哈哈哈,🆘。。
-
……
Promise
(见另一篇笔记)
异步函数(async/await function)
async/await的目的在于简化使用基于 promise 的 API 时所需的语法。async/await的行为就好像搭配使用了生成器和 promise。
async/await 其实就是语法糖,它们的异步底层原理和 Promise 是一样的。所以在搞清楚 Promise 后,我们学习 async/await 的主要目的就是:如何使用,以及、如何更优雅地写异步函数?
如何使用?
相比于
promise.then,await只是获取 promise 的结果的一个更优雅的语法。并且也更易于读写。……另外,我们需要在函数前面加上
async关键字,以使它们能工作;我们需要用await替换掉.then的调用。 ——https://zh.javascript.info/async-await
async/await只是对 Promise 的语法糖 ——让你用同步写法表达异步逻辑,读起来更直观。
注意!!:async 定义的是一个 函数 —— 不调用函数本身的话,再多的 await 也看不到异步的执行结果!
【补】async/await 与 Promise 链的思维区别
(之前就总觉得奇怪:async/await 说是替换 Promise then 即可,但写起来总感觉 “懵逼、没思路” ……)
这里先放省流总结:
| 思维层面 | Promise then | async/await |
|---|---|---|
| 代码风格 | 回调驱动 | 流程驱动 |
| 控制权 | 浏览器控制“何时继续” | 你控制“暂停点” |
| 可读性 | 分散在多个 .then() 里 |
像写同步代码一样从上到下 |
| 思维方式 | “异步任务完成后做什么” | “执行到这一步时先等结果再走” |
具体来讲(其实是之前思考的思路):
async/await 写法:你写代码 → 遇到 await 时“暂停执行” → 等待异步结果 → 再继续往下写逻辑
(有种 “红灯停,绿灯行” 的感觉。但传统的 Promise 会经过一个个明显的十字路口、帮你分清;async/await 代码格式更美观,但像一条直通罗马的大路,哪里停哪里行、需要自己分派得更清楚。)
——其实就是相比 Promise then ,我们需要更清楚地拆分异步任务 哪里停、哪里继续走?
我觉得:
- Promise then 因为是一块儿一块儿的,写到一个 then 块的结尾、return 一个 Promise 给下一节时,就是在提示你 “这一块结束了!”,有始有终的每一小节,给人感觉很有条理、容易掌控(容易拆解?)
- 但 async/await 就是在一整块儿里写完所有逻辑,就像写数学大题一样、、容易乱(而 Promise then 则像短文填空)?
- 什么时候加 await ?
- 什么时候要传 Promise ?
这些东西,没有清晰的函数链告诉你了。你必须自己来安排这些东西。所以容易乱?
- Promise 链 提供了“结构性提示”(每一节明确以
.then()为界),逻辑的分块让人能自然地理解异步的先后。- async/await 则把结构感隐藏在语法糖里,换来更接近同步的阅读体验,但同时也要求开发者主动维护逻辑的节奏(什么时候等待、什么时候返回 Promise)。
Promise 链像“搭积木”,结构清晰;
async/await 像“一口气写作文”,流畅但容易失控。
接下来会更偏 “底层分析”:
async
在 function xxx() 前使用,标明 “这是一个异步函数” :
1
——那你就会好奇了:为啥就在函数开头加了 5 个 ”神奇字母“ ,函数就能从同步变异步了?这其中发生了甚么??
虽然我没看懂具体语法细节但是!我觉得主要是 3 步走:
- Generator(生成器)函数有个特殊机制,能让函数内部 “暂停”,之后再回到原来执行的地方;
- 进阶一:接着让程序能自己自动(回到暂停地点?)执行,无需人来手动开关;
- 进阶二:再加个 Promise,形成异步链。
这样,就能看出 fetch 所有基本功能的雏形了!是不是很简单。
-
机制对照(看着留个印象就行):
你总结的阶段 实际机制 核心说明 1. Generator 能暂停函数 ✔️ Generator 函数 ( function*)Generator 允许函数执行到 yield暂停,并且下次.next()时从暂停点继续执行。它本质上就是一种可中断的执行上下文。2. 自动执行(不再手动 next()) ✔️ “自执行器” (比如 co库)早期需要人写“自动执行器”,让 Generator 自动 .next()。这样才能让它像正常函数一样连续跑。3. 加 Promise,形成异步链 ✔️ async/await = Generator + Promise + 自动执行 async函数底层其实就是用 Promise 包装、并在内部隐式调用类似co的逻辑,让异步操作(await 后面)暂停、恢复。 -
async/await就像两个 “标志 / 标识符” ;当你在函数前(中)加上这个标识符时,JS 就会在进入底层编译时,将函数代码自动转换为(类似以下的格式):1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// async 版本 async function foo() { const data = await fetchData(); console.log(data); } // 编译后(伪代码) function foo() { return new Promise((resolve, reject) => { const gen = function* () { try { const data = yield fetchData(); // 暂停点,在 await 处暂停 console.log(data); resolve(); // 结束时 resolve(传递 Promise 对象-状态) } catch (e) { reject(e); } }(); // 自动执行器: 帮我们自动从暂停的 await 处继续往下执行 function step(nextF, arg) { ... } step(gen.next); }); }