同步、异步、Promise、then、async/await

一、同步与异步的本质区别

同步和异步是编程中两种不同的执行模式,理解它们的区别是掌握异步编程的基础。

同步(Synchronous):代码按照书写顺序依次执行,前一个操作完成才能进行下一个操作。例如:

javascript 复制代码
console.log('第一行');
console.log('第二行'); 
// 输出顺序永远是"第一行"然后"第二行"

异步(Asynchronous):代码的执行不按照书写顺序,某些操作会被放入任务队列,等待主线程空闲时执行。例如:

javascript 复制代码
console.log('开始');
setTimeout(() => console.log('定时器回调'), 0);
console.log('结束');
// 输出顺序: 开始 -> 结束 -> 定时器回调

异步操作常见于:

  • 网络请求(AJAX/fetch)
  • 定时器(setTimeout/setInterval)
  • 文件读写(Node.js)
  • 数据库操作

关键区别:同步会阻塞代码执行,异步不会阻塞后续代码的执行。

二、回调地狱与Promise的诞生

回调地狱问题

在Promise出现之前,异步操作主要依赖回调函数,多层嵌套会导致"回调地狱":

javascript 复制代码
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      // 更多嵌套...
    });
  });
});

这种代码难以阅读、调试和维护,错误处理也非常复杂。

Promise的解决方案

Promise是ES6引入的异步编程解决方案,它通过链式调用解决了回调地狱问题。

Promise三大状态

  • Pending(进行中)
  • Fulfilled(已成功)
  • Rejected(已失败)

状态一旦改变就不可逆(Pending → Fulfilled 或 Pending → Rejected。

基本用法

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功 */) {
    resolve(value); // 状态变为Fulfilled
  } else {
    reject(error);  // 状态变为Rejected
  }
});

promise.then(
  value => { /* 成功处理 */ },
  error => { /* 失败处理 */ }
);

链式调用示例

javascript 复制代码
function p1() {
  return new Promise(resolve => {
    setTimeout(() => resolve('结果1'), 1000);
  });
}

function p2(data) {
  return new Promise(resolve => {
    setTimeout(() => resolve(data + ' 结果2'), 1000);
  });
}

p1()
  .then(p2)
  .then(result => console.log(result))
  .catch(error => console.error(error));
// 2秒后输出: "结果1 结果2"

Promise的链式调用使异步代码有了同步代码的阅读体验。

三、async/await:Promise的语法糖

async/await是ES2017引入的语法,基于Promise实现,但代码更加简洁。

async函数

  • async函数总是返回一个Promise对象
  • 如果返回值不是Promise,会自动包装成Promise
javascript 复制代码
async function foo() {
  return 'hello';
}
// 等价于
function foo() {
  return Promise.resolve('hello');
}

await表达式

  • await后面通常是一个Promise对象
  • 会"暂停"async函数的执行,等待Promise解决
  • 如果是rejected状态,会抛出异常
javascript 复制代码
async function getData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

async/await与Promise的关系

  1. async函数返回的是Promise对象
  2. await后面可以接任何thenable对象(有then方法的对象)
  3. async/await底层是基于Promise和生成器实现的语法糖

对比示例

Promise版本:

javascript 复制代码
function loadData() {
  return fetchData()
    .then(data => processData(data))
    .then(result => displayResult(result))
    .catch(error => handleError(error));
}

async/await版本:

javascript 复制代码
async function loadData() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    displayResult(result);
  } catch (error) {
    handleError(error);
  }
}

async/await让异步代码看起来更像同步代码,提高了可读性。

四、错误处理机制对比

Promise的错误处理

  1. 使用.catch()捕获错误:
javascript 复制代码
somePromise()
  .then(handleSuccess)
  .catch(handleError);
  1. 或者在then中传入第二个参数:
javascript 复制代码
somePromise().then(
  handleSuccess,
  handleError
);

async/await的错误处理

使用try/catch结构:

javascript 复制代码
async function foo() {
  try {
    const result = await somePromise();
    // 处理结果
  } catch (error) {
    // 处理错误
  }
}

五、实际应用场景

适合Promise的场景

  1. 需要并行执行多个异步操作时:
javascript 复制代码
Promise.all([asyncTask1(), asyncTask2(), asyncTask3()])
  .then(([result1, result2, result3]) => {
    // 所有任务都完成后的处理
  });
  1. 需要竞速执行多个异步操作时:
javascript 复制代码
Promise.race([fetch1, fetch2])
  .then(firstResult => {
    // 使用最先返回的结果
  });

适合async/await的场景

  1. 有前后依赖关系的异步操作:
javascript 复制代码
async function processUserData(userId) {
  const user = await getUser(userId);
  const orders = await getOrders(user.id);
  const payments = await getPayments(user.id);
  return { user, orders, payments };
}
  1. 需要更清晰错误处理的复杂异步流程。

六、性能考量

  1. Promise创建后会立即执行,无法取消
  2. async/await不会带来额外性能开销,它只是语法糖
  3. 过度使用await会导致不必要的等待:
javascript 复制代码
// 不推荐 - 顺序执行
const a = await getA();
const b = await getB(); 

// 推荐 - 并行执行
const [a, b] = await Promise.all([getA(), getB()]);

七、总结与最佳实践

  1. 演进历程:回调 → Promise → async/await
  2. 关系
    • Promise是async/await的基础
    • async函数返回Promise
    • await等待Promise解决
  3. 最佳实践
    • 简单异步操作可直接使用Promise
    • 复杂异步流程推荐async/await
    • 并行无依赖任务使用Promise.all
    • 始终处理错误(.catch或try/catch)
  4. 注意事项
    • 避免在循环中错误使用await
    • 合理使用Promise缓存避免重复请求
    • 在Node.js中注意Promise的内存泄漏问题

通过合理结合Promise和async/await,可以编写出既高效又易于维护的异步JavaScript代码。

相关推荐
yinuo6 分钟前
纯CSS&JS实现:丝滑渐变过渡的动态导航栏
前端
qq. 28040339847 分钟前
vue介绍
前端·javascript·vue.js
未来之窗软件服务11 分钟前
未来之窗昭和仙君(五十五)标签票据打印模板设计器——东方仙盟筑基期
前端·打印设计器·仙盟创梦ide·东方仙盟·昭和仙君·东方仙盟架构
Mr.Jessy20 分钟前
Web APIs 学习第五天:日期对象与DOM节点
开发语言·前端·javascript·学习·html
前端大卫21 分钟前
如何统一前端项目的 Node 版本和包管理器?
前端
Hi~晴天大圣1 小时前
HTML onclick用法
前端·html
速易达网络1 小时前
HTML<output>标签
javascript·css·css3
!win !2 小时前
前端跨标签页通信方案(上)
前端·javascript
xw52 小时前
前端跨标签页通信方案(上)
前端·javascript
烛阴2 小时前
Python数据可视化:从零开始教你绘制精美雷达图
前端·python