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

相关推荐
Yolo@~1 小时前
个人网站:基于html、css、js网页开发界面
javascript·css·html
斯~内克1 小时前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
数据知道1 小时前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
清风絮柳1 小时前
51. “闲转易”交易平台小程序(基于springboot&vue)
vue.js·spring boot·小程序·毕业设计·校园二手交易平台·二手交易小程序·闲转易交易系统
dr李四维1 小时前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久1 小时前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain2 小时前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏2 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
GISer_Jing2 小时前
React-Markdown详解
前端·react.js·前端框架
太阳花ˉ2 小时前
React(九)React Hooks
前端·react.js