为什么需要异步编程?

JavaScript 是单线程的。这意味着它一次只能做一件事。如果所有操作都是同步的,那么当遇到一个耗时很长的操作(比如网络请求、读取大文件、定时器)时,线程就会被“阻塞”,浏览器就会卡住,用户无法进行任何操作,体验极差。

异步编程就是为了解决单线程语言带来的“阻塞”问题。 它将耗时的操作“挂起”,先执行后面的代码,等这些耗时操作完成了,再回过头来执行相应的逻辑。


前端异步编程的演进史

前端异步编程主要经历了以下几个阶段:

1. 回调函数 (Callback)

这是最古老、最基础的异步解决方案。

原理:将一个函数(回调函数)作为参数传递给另一个函数,这个函数会在异步操作完成后被调用。

示例

// 模拟一个异步操作,比如读取文件
function readFile(callback) {
  setTimeout(() => {
    const data = '这是文件内容';
    callback(null, data); // 第一个参数是错误(如果有),第二个是数据
  }, 1000);
}

// 使用回调函数
readFile((err, data) => {
  if (err) {
    console.error('出错啦:', err);
    return;
  }
  console.log('文件内容是:', data);
});
console.log('我先执行了,不会等待readFile完成');

缺点(回调地狱 Callback Hell)
当多个异步操作存在依赖关系时,代码会变得层层嵌套,难以阅读和维护。

doSomething((result1) => {
  doSomethingElse(result1, (result2) => {
    doThirdThing(result2, (result3) => {
      console.log('最终结果:' + result3);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);
2. Promise (ES6)

Promise 是一个对象,它代表了一个异步操作的最终完成(或失败) 及其结果值。它是对回调方案的一种革命性改进。

核心状态

  • pending:初始状态,既不是成功,也不是失败。

  • fulfilled:操作成功完成。

  • rejected:操作失败。

优点

  • 链式调用 (Chaining):解决了回调地狱的问题。

  • 错误冒泡:错误可以一直向后传递,直到被捕获。

示例

function readFile() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true; // 模拟成功或失败
      if (success) {
        resolve('这是Promise的文件内容'); // 成功时调用
      } else {
        reject('读取文件失败'); // 失败时调用
      }
    }, 1000);
  });
}

// 使用Promise
readFile()
  .then((data) => {
    console.log('文件内容是:', data);
    return processData(data); // 返回一个新的Promise,继续链式调用
  })
  .then((processedData) => {
    console.log('处理后的数据:', processedData);
  })
  .catch((err) => {
    // 捕获前面任何一步发生的错误
    console.error('出错啦:', err);
  })
  .finally(() => {
    console.log('无论成功失败,我都会执行');
  });

console.log('我先执行了');
3. Async/Await (ES2017)

这是建立在 Promise 之上的语法糖。它让异步代码的写法看起来和同步代码一样,极大地增强了代码的可读性。

  • async:声明一个函数是异步的。这个函数总会返回一个 Promise。

  • await:只能在 async 函数内部使用。它会“等待”一个 Promise 完成并返回其结果,然后再继续执行后面的代码。

示例

// 定义一个返回Promise的函数
function readFile() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('这是Async/Await的文件内容');
    }, 1000);
  });
}

// 使用 async/await
async function main() {
  try {
    console.log('开始读取...');
    const data = await readFile(); // 等待Promise解决,拿到结果后再继续
    console.log('文件内容是:', data);

    const processedData = await processData(data); // 可以继续await下一个异步操作
    console.log('处理后的数据:', processedData);

  } catch (err) {
    // 使用 try...catch 来捕获错误
    console.error('出错啦:', err);
  }
}

main();
console.log('我不会被await阻塞,因为main是异步的');

优点

  • 代码清晰:彻底摆脱了回调函数和 .then() 链,逻辑像同步代码一样直白。

  • 错误处理:可以使用熟悉的 try...catch 语法。


其他重要的异步概念和API

  1. 事件循环 (Event Loop)
    这是 JavaScript 实现异步的核心机制。它负责监听调用栈和任务队列(微任务、宏任务),一旦调用栈为空,就将队列中的任务推入调用栈执行。理解事件循环是深入理解异步的关键。

  2. 宏任务 (MacroTask) 与微任务 (MicroTask)

    • 宏任务setTimeoutsetIntervalsetImmediate, I/O, UI rendering。

    • 微任务Promise.then/catch/finallyprocess.nextTick (Node.js), MutationObserver
      执行顺序每执行一个宏任务后,都会清空整个微任务队列。这个规则决定了 Promise 总是比 setTimeout 先执行。

  3. Promise 工具方法

    • Promise.all([promise1, promise2, ...]):等待所有 Promise 都成功完成,返回一个结果数组。如果有一个失败,整个 all 就立即失败。

    • Promise.race([promise1, promise2, ...]):竞速,哪个 Promise 先完成(成功或失败)就返回哪个的结果。

    • Promise.allSettled([promise1, promise2, ...]) (ES2020):等待所有 Promise 都完成(无论成功失败),返回一个包含每个 Promise 结果的对象数组。

    • Promise.any([promise1, promise2, ...]) (ES2021):等待第一个成功的 Promise,如果全部失败,则返回一个聚合错误。

总结与最佳实践

特性 回调函数 (Callback) Promise Async/Await
可读性 差(回调地狱) 好(链式调用) 极好(同步风格)
错误处理 麻烦(需手动传递) 好(.catch() 极好try...catch
调试 困难 较好 很好(错误堆栈清晰)
流行度 旧代码/特定API 现代基础 现代首选

最佳实践

  1. 彻底摒弃回调地狱,不要再编写深层嵌套的回调代码。

  2. 首选 Async/Await 来编写新的异步逻辑,它让代码最易读、最易维护。

  3. 理解 Promise 是基础,因为 Async/Await 是基于 Promise 的。

  4. 合理使用 Promise.all 等工具方法来处理并发异步操作。

  5. 一定要做好错误捕获,使用 .catch() 或 try...catch


往期目录速览:

      🤙《中级前端进阶方向指南》

《中级前端进阶方向 第一步)JavaScript 微任务 / 宏任务机制》

《中级前端进阶方向 第二步)深入事件循环(Event Loop)》

《中级前端进阶方向 第三步)深入javascript原型链,附学习代码》

《中级前端进阶方向 第四步)深入javascript 闭包、this 、作用域提升,附学习案例源码》

《​​中级前端进阶方向 第五步)深入 javascript高级特性Proxy,附学习案例源码》

《​​中级前端进阶方向 第六步)深入 javascript 高级特性Generator生成器,附案例源码》
《​​中级前端进阶方向 第七步)深入 javascript 高级特性Async/Await,附源码》

《​​中级前端进阶方向 第八步)深入 javascript 高级特性 Promise,附案例源码》

《​​中级前端进阶方向 第九步)深入 javascript 高级特性 模块化 与 装饰器,附案例源码》

《中级前端进阶方向 第十步)深入 浏览器性能优化(渲染机制、内存泄漏..),附案例源码》


--🖍《CSS》-----------------------------------------------------------------------------------------

《中级前端进阶 第十一步) 深入CSS3 新特性,附源码,可视化演示等》

《中级前端进阶 第十二步) 深入CSS3 Flex布局,附源码,可视化演示等》

《中级前端进阶 第十三步) 深入CSS3 Grid布局,附源码,可视化演示等》

《中级前端进阶 第十四步) 深入CSS3 响应式设计,附源码,可视化演示等》

《中级前端进阶 第十五步) 深入CSS 动态主题切换,附源码,可视化演示等》

《中级前端进阶 第十六步) 深入CSS 动画,附源码,可视化演示等》

《中级前端进阶 第十七步) 深入CSS 性能优化,附源码,可视化演示等》


--🖍《框架篇》-----------------------------------------------------------------------------------------

《中级前端进阶方向 框架篇第十八步) javascript proxy技术点,加可视化演示》
《中级前端进阶方向 框架篇第十九步) Vue3 响应式 可视化演示》
《中级前端进阶方向 框架篇第二十步) Virtual DOM,可视化演示》
《中级前端进阶方向 框架篇第二十一步) Diff算法,可视化演示》

《中级前端进阶方向 框架篇第二十二步) Diff算法,可视化演示》

《中级前端进阶方向 框架篇第二十三步) 并行(Parallelism)》

《中级前端进阶方向 框架篇第二十四步) 并发(Concurrency)》


--🖍《延伸扩展》-----------------------------------------------------------------------------------------
《中级前端进阶方向 延伸扩展一)javascript 私有状态/工厂函数/回调中保持局部状态/防抖与节流/IIFE 捕获》

《中级前端进阶方向 延伸扩展二) Promise API 概览,可视化演示页面》《中级前端进阶 延伸扩展三) CSS单位大全,附源码,可视化演示等》
《中级前端进阶方向 延伸扩展四) async/await 和事件循环的关系》
《中级前端进阶方向 延伸扩展五) CSS | GPU 加速、will-change》
《中级前端进阶方向 延伸扩展六) javascript 枚举(Enumeration)》

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐