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 个演进知识点。 - 九世師 - 博客园

JavaScript 高级特性完全指南:从 ES6 到 ES2025 的 10 个核心知识点! - 九世師 - 博客园

仅用一个配置,Webpack 打包速度暴涨 1000%! - 九世師 - 博客园

posted @ 2026-05-20 18:39  九世師  阅读(12)  评论(0)    收藏  举报