别再乱用异步了!一文搞懂 Promise 和 async/await 的执行顺序与最佳实践

在前端开发中,异步操作随处可见:发起 HTTP 请求、操作本地文件、加载图片、定时动画......如果你没弄懂 JavaScript 中异步代码的执行顺序,踩坑只是早晚的事。

这篇文章就带你从最基础的异步执行流程讲起,深入理解 Promise 和 async/await 的用法和区别,最后再聊聊一些真实项目里如何优雅控制异步任务。


一、别把 setTimeout 当成异步的代表

我们经常用 setTimeout 举例异步,其实它更多是模拟。来看个简单例子:

js 复制代码
setTimeout(() => {
  console.log('异步任务');
}, 10);

console.log('同步任务开始');
for (let i = 0; i < 3; i++) {
  console.log(`循环 ${i}`);
}

输出顺序:

复制代码
同步任务开始  
循环 0  
循环 1  
循环 2  
异步任务

为什么 setTimeout 被推迟了?

因为 JavaScript 采用事件循环机制:同步任务放在主线程上执行,异步任务(如定时器、网络请求)被注册后挂到任务队列中,等主线程空闲时再执行。


二、Promise 到底解决了什么问题?

在没有 Promise 之前,我们处理异步依赖逻辑时经常这么写:

js 复制代码
setTimeout(() => {
  console.log('第一个异步');
  setTimeout(() => {
    console.log('第二个异步');
  }, 1000);
}, 1000);

层层嵌套,看着就头疼。这就是回调地狱。

为了解决这个问题,ES6 引入了 Promise

js 复制代码
const p = new Promise((resolve) => {
  console.log('开始异步任务...');
  setTimeout(() => {
    resolve('成功');
  }, 1000);
});

p.then((res) => {
  console.log('任务完成:', res);
});

输出顺序:

makefile 复制代码
开始异步任务...
任务完成: 成功

Promise 的基本结构

js 复制代码
const promise = new Promise((resolve, reject) => {
  // 异步任务逻辑
  resolve(结果);  // 或 reject(错误)
});

promise.then(res => {
  // 处理结果
}).catch(err => {
  // 处理异常
});

你可以理解成:我先答应你这个事(promise),等完成了我再通知你(then)


三、async/await:把异步写成"看起来是同步的"

虽然 .then() 写起来比回调舒服多了,但当你有多个异步串行执行时,链式结构依然会变得繁琐:

js 复制代码
loadUser()
  .then(user => getProfile(user.id))
  .then(profile => updateUI(profile))
  .catch(err => console.error(err));

换成 async/await,逻辑清晰很多:

js 复制代码
async function init() {
  try {
    const user = await loadUser();
    const profile = await getProfile(user.id);
    updateUI(profile);
  } catch (err) {
    console.error(err);
  }
}

init();

async/await 本质上还是基于 Promise

只是语法糖,帮你隐藏了 .then(),让代码看起来像同步。

js 复制代码
(async function () {
  const result = await new Promise(resolve => {
    setTimeout(() => resolve('任务完成'), 1000);
  });
  console.log(result);
})();

输出:

复制代码
任务完成

四、别踩这些 async/await 的坑!

  1. await 只能在 async 函数中使用

    否则会报错:SyntaxError: await is only valid in async functions

  2. await 会阻塞当前 async 函数,但不会阻塞主线程

    意思是你函数后面那几行不会继续执行,直到这个 Promise 完成。

  3. 多个异步任务并不适合串行执行

    如果几个请求可以并发,那就用 Promise.all()

    js 复制代码
    const [res1, res2] = await Promise.all([
      fetch('/api/a'),
      fetch('/api/b')
    ]);

五、真实案例:加载 GitHub 仓库列表

html 复制代码
<ul id="repos"></ul>
<button id="btn">加载仓库</button>

<script>
document.getElementById('btn').addEventListener('click', async () => {
  const res = await fetch('https://api.github.com/users/sleep202411/repos');
  const data = await res.json();

  document.getElementById('repos').innerHTML = data.map(item => `
    <li><a href="${item.html_url}" target="_blank">${item.name}</a></li>
  `).join('');
});
</script>

这段代码的执行流程:

  1. 用户点击按钮
  2. 发起请求(异步)
  3. 等待响应(await)
  4. 渲染到页面

看起来就像同步逻辑,这是 async/await 的最大魅力所在。


六、Node.js 场景:读取文件内容

用原始写法:

js 复制代码
const fs = require('fs');

fs.readFile('./1.html', (err, data) => {
  if (err) throw err;
  console.log(data.toString());
});

Promise 化后:

js 复制代码
const fs = require('fs');

const readFilePromise = new Promise((resolve, reject) => {
  fs.readFile('./1.html', (err, data) => {
    if (err) reject(err);
    else {
      console.log(data.toString());
      resolve();
    }
  });
});

readFilePromise.then(() => {
  console.log('文件读取完毕');
});

再用 async/await 简化:

js 复制代码
const fs = require('fs/promises');

(async function () {
  const content = await fs.readFile('./1.html', 'utf-8');
  console.log(content);
})();

七、总结一下:Promise vs async/await 该怎么选?

场景 推荐用法
简单异步流程 Promise .then()
多步骤串行依赖异步任务 async/await
多个异步任务并发执行 Promise.all()
错误处理要统一 try/catch + await

最后的提醒

很多前端同学对 async/await 的理解还停留在"可以简化代码"的阶段,但实际上,你必须清楚它的执行顺序、Promise 本质和异步队列的原理,才能在复杂项目中灵活使用。

希望这篇文章帮你把异步代码真正"搞明白",以后在调接口、加载数据、处理任务时不再乱用异步逻辑。

相关推荐
霸王蟹40 分钟前
带你手写React中的useReducer函数。(底层实现)
前端·javascript·笔记·学习·react.js·typescript·前端框架
托尼沙滩裤1 小时前
【Vue3】实现屏幕共享惊艳亮相
前端·javascript·vue.js
啃火龙果的兔子1 小时前
前端八股文-vue篇
前端·javascript·vue.js
Mintopia1 小时前
计算机图形学环境贴图(Environment Mapping)教学指南
前端·javascript·计算机图形学
shenyan~1 小时前
关于 WASM: WASM + JS 混合逆向流程
开发语言·javascript·wasm
Mintopia1 小时前
Three.js 高级纹理(Advanced Textures):超越基础,打造沉浸式 3D 世界
前端·javascript·three.js
玄玄子1 小时前
JS Promise
前端·javascript·程序员
Raink老师2 小时前
7. TypeScript接口
javascript·typescript
Thanks_ks2 小时前
探索现代 Web 开发:从 HTML5 到 Vue.js 的全栈之旅
javascript·vue.js·css3·html5·前端开发·web 开发·全栈实战
GIS之路2 小时前
OpenLayers 获取地图状态
前端·javascript·html