JS异步四兄弟成长记:9个关键升级让你少写回调!
大家好,我是King。
今天咱们聊点实在的。很多刚入门前端的朋友,看到异步编程这几个字就头大。Promise、Generator、Async/Await、Async迭代器,一个比一个绕。其实说白了,它们就是JavaScript为解决“等一等”这个问题,一步步进化出来的四个版本。
我尽量用大白话,把这四个“人物”的9个核心进化点给你讲清楚。看完你就能明白,为什么异步代码越写越像同步代码,越写越舒服。
第一幕:Promise —— 告别回调地域
进化点1:从“层层嵌套”到“链条串联”
以前咱们写异步,是这样的:
// 老办法:回调里面套回调
getData(function(a){
getMore(a, function(b){
getResult(b, function(c){
console.log(c);
});
});
});
这玩意儿看着就头疼,改个bug得找半天括号。
Promise来了之后,变成这样:
getData()
.then(a => getMore(a))
.then(b => getResult(b))
.then(c => console.log(c));
一层一层往下接,清晰多了。这就是Promise的第一个功劳——把嵌套拍平了。
进化点2:错误处理统一入口
以前每个回调函数里都要自己处理错误,漏一个程序就可能崩。Promise提供了一个catch方法,所有错误都能被它接住:
getData()
.then(a => getMore(a))
.then(b => getResult(b))
.catch(err => console.log('哪里出错了', err));
不管链条哪一环出了问题,最后都能被catch捕获到。不用每个环节都写if(err),省心不少。
进化点3:多个异步任务可以“等大家一起完成”
有时候我们需要同时请求多个接口,等所有数据都回来了再统一处理。Promise提供了两个帮手:
Promise.all:等所有任务都完成
Promise.race:哪个任务先完成就用哪个结果
Promise.all([getUser(), getScore(), getLevel()])
.then(([user, score, level]) => {
// 三个数据都到手了
});
这个进化让并行请求变得特别顺手。
第二幕:Generator —— 给函数装个“暂停键”
进化点4:函数执行到一半,可以停下来歇会儿
Promise虽然解决了回调嵌套的问题,但写起来还是有点“链式”的味道,不够直接。Generator带来了一个新思路:函数可以分段执行。
function* stepByStep() {
let a = yield getData();
let b = yield getMore(a);
let c = yield getResult(b);
return c;
}
yield就像一个暂停标记,执行到这儿先停住,等拿到结果了再继续往下走。这让异步代码看起来更像同步代码了——从上往下读,不再跳来跳去。
进化点5:外部可以往函数里“递东西”
Generator的另一个特点,是调用方可以往暂停的地方传值进去:
let gen = stepByStep();
gen.next().value.then(res1 => {
gen.next(res1).value.then(res2 => {
gen.next(res2);
});
});
虽然这个写法本身还是有点绕,但它打开了一扇门:异步流程可以被外部控制。后面Async/Await就是在这个基础上做出来的。
第三幕:Async/Await —— 终于像写同步代码了
进化点6:不用再写.then了
Async/Await可以说是Generator + Promise的“官方甜点版”。它把异步代码彻底伪装成了同步代码的模样:
async function getFinalResult() {
let a = await getData();
let b = await getMore(a);
let c = await getResult(b);
console.log(c);
}
看到没?await后面跟一个Promise,代码就停在这里等结果,结果回来了再赋值给变量。不用.then,不用yield,就是普通的赋值语句。这才是程序员最舒服的写法。
进化点7:错误可以用try/catch直接抓
之前Promise的错误用.catch,现在Async/Await允许你用最传统的try/catch来抓异常:
async function getFinalResult() {
try {
let a = await getData();
let b = await getMore(a);
let c = await getResult(b);
console.log(c);
} catch (err) {
console.log('出错啦', err);
}
}
同步代码怎么写,异步代码就怎么写。学习成本降到了最低。
进化点8:await可以等任何东西
await后面不一定要跟Promise,跟一个普通值也行,它会自动给你包成一个Promise。这意味着你不用担心传入的东西“不标准”,await会自己处理。
let x = await 123; // 没问题,x就是123
let y = await someFunc(); // someFunc返回Promise或普通值都行
第四幕:Async迭代器 —— 处理“一个个来的数据流”
进化点9:边来边处理,不用等全部到齐
之前的异步处理,大多是一次性拿完所有数据。但如果数据是流式过来的呢?比如分页加载、WebSocket推送、读取大文件。
Async迭代器就是为这种场景准备的。它让你可以用for await...of循环,来一个处理一个:
async function processStream() {
for await (let chunk of getDataStream()) {
console.log('拿到一块数据', chunk);
}
}
每次循环会等着下一个数据块到来,来了就处理,然后继续等。内存占用低,响应也快。跟普通for...of几乎一样的写法,只是多了一个await关键词。
一张图总结这9个演进点
演进点 解决的问题 代表技术
1. 链条串联 回调嵌套太深 Promise.then
2. 统一错误处理 每个回调都要单独处理错误 Promise.catch
3. 并行等待 多个异步任务一起等 Promise.all/race
4. 函数可暂停 代码不能分段执行 Generator + yield
5. 外部传值 无法控制异步流程的输入 Generator.next传参
6. 同步写法 链式调用还是不够直白 Async/Await
7. try/catch抓错 Promise的catch不够传统 Async/Await + try
8. await兼容任意值 Promise类型不统一导致报错 await自动包装
9. 流式处理 数据是一块一块来的 for await...of
写在最后
这四个家伙,其实是一步步“打补丁”打出来的。
Promise:把回调拍平,解决了“回调地域”的视觉问题。
Generator:给了函数暂停和恢复的能力,为后面铺路。
Async/Await:把异步彻底“同步化”,是目前最舒服的写法。
Async迭代器:处理流式数据,一个接一个地拿。
日常开发中,90%的场景用async/await就够了。遇到流式数据或者分页加载,再用for await...of。Promise的all和race偶尔也会用。Generator现在用得相对少一些,但它的设计思想很重要。
希望这篇文章能帮你把这几个概念理清楚。有疑问欢迎留言交流,我是King,下次见。
更多经验分享链接:
异步编程完全指南:从 Promise 到 await 与 Async 迭代器的 8 个演进知识点。 - 九世師 - 博客园

浙公网安备 33010602011771号