同步、异步、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代码。

相关推荐
姑苏洛言几秒前
在开发扫码小程序中,遇到胡坑“require() 默认不支持绝对路径”及其解决方案
前端
Passerby_K1 分钟前
vue3+dhtmlx 甘特图真是案例
前端·vue·甘特图
佳腾_7 分钟前
【Web应用服务器_Tomcat】二、Tomcat 核心配置与集群搭建
java·前端·中间件·tomcat·web应用服务器
brzhang37 分钟前
代码即图表:dbdiagram.io让数据库建模变得简单高效
前端·后端·架构
三巧1 小时前
纯CSS吃豆人(JS仅控制进度)
javascript·css·html
SummerGao.1 小时前
【解决】layui layer的提示框,弹出框一闪而过的问题
前端·layui
软件技术NINI1 小时前
html css js网页制作成品——HTML+CSS+js美甲店网页设计(5页)附源码
javascript·css·html
天天扭码1 小时前
从数组到对象:JavaScript 遍历语法全解析(ES5 到 ES6 + 超详细指南)
前端·javascript·面试
拉不动的猪1 小时前
前端开发中常见的数据结构优化问题
前端·javascript·面试
街尾杂货店&1 小时前
css word
前端·css