# JavaScript 的“等等我”:聊聊同步与异步

很多人学 JavaScript 的时候,都会突然意识到一点

明明代码写在前面,为什么后面的先执行?明明发了请求,为什么拿不到数据?


从一段奇怪的输出说起

先看一段代码:

js

javascript 复制代码
console.log('start');

setTimeout(() => {
  console.log('222');
}, 1000);

console.log('end');

我觉得输出应该是start222end,但实际运行起来是:

sql 复制代码
start
end
222

222却跑到最后一次。


JS为什么是单线程?

要理解这件事,得先知道 JS 是怎么设计的。

Java、C++这类语言支持多线程------可以同时跑好几条运行路线,效率高,但写起来复杂,要手动处理各种竞争、锁、死锁的问题。

JS 的设计者当年做了一个取舍:让 JS 简单,单线程。一次只做一件事,从上到下顺序执行,不用操心多线程带来的那些麻烦。

这对写界面来说其实是相当合理的。大多数代码执行起来很快,压根不需要方程。

但问题来了------网络请求要等,计时器要等,用户点击也要等,这些"等"的事情,单线程总不能真的傻坐在那等吧?


事件循环:JS的"排队机制"

JS 的解决方案称为Event Loop(事件循环)

规则很简单:

  1. 同步代码,直接执行,从上到下,一行不落
  2. 遇到异步任务(setTimeoutfetch、事件监听......),把它"记下来",先跳过
  3. 等同步代码全部跑完,再回头,把前面记下的异步任务一个提出来执行

到底看最开始那段代码,setTimeout就是异步任务。JS看到它,把里面的回调函数调用一个"等候区",继续往下跑console.log('end')。等这些同步的跑完了,再回头把222取出来执行。

所以end222前面先执行


Promise:更优雅地控制异步流程

光有Event Loop还不够。实际上经常要处理这样的情况:

先请求用户列表,随后获取用户ID,再去请求该用户的详情。

如果全靠调用回调来写,代码会越套越深,难看又难维护。于是 ES6 带来了Promise

Promise的中文意思是"承诺"------它代表一个未来会有结果的异步任务

实例化一个Promise需要传一个函数(叫executor),这个函数会立刻执行,里面放运行操作:

js

javascript 复制代码
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(666);       // 成功,把结果传出去
    // reject('网络错误');  // 失败,把原因传出去
  }, 2000);
});

承诺有清晰状态:

  • 待处理:还在等,任务尚未完成
  • 已完成 :成功了,resolve()被调用
  • 被拒绝 :失败了,reject()被调用

状态一旦改变,就不会再变。

获得结果的方式是链式调用:

js

javascript 复制代码
p.then(data => {
    console.log('成功:', data);
  })
  .catch(err => {
    console.log('失败:', err);
  })
  .finally(() => {
    console.log('无论如何都会跑到这里');
  });

.then处理成功,.catch处理失败,.finally无论如何都执行------比如关闭加载动画,无论请求成没成功都得关。


用 Promise 造一个睡眠

JS 原生没有sleep()这个东西,但可以用 Promise 自己造一个:

js

javascript 复制代码
function sleep(t) {
  return new Promise(resolve => {
    setTimeout(resolve, t);
  });
}

sleep(2000).then(() => {
  console.log('2秒后才到这里');
});

大概代码做的事情是:创建一个 Promise,2 秒后调用resolve(),于是.then里的内容在 2 秒后执行。

虽然语言没有内置的阻塞,但通过Promise链,同样能控制"等多久再做什么"。


一个常见的坑

console.log里直接包了fetch

js

sql 复制代码
console.log(fetch('https://api.example.com/data'));

结果打印出来的不是数据,而是一个 Promise 对象。

这是因为fetch是异步的,它立即返回的是一个 Promise(状态还是 Pending),数据还没有回来。要获取数据,必须.then等它完成:

js

ini 复制代码
fetch('https://api.example.com/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log('请求出错', err));

小结

JS 的这套机制,核心就是三句话:

  1. 单线程,同步代码老老实实从上到下跑
  2. 事件循环,异步任务先放一边,等同步跑完再结局处理
  3. Promise ,用来优雅地描述"异步任务的结果",成功走.then,失败走.catch
相关推荐
朦胧之4 小时前
页面白屏卡住排查方法
前端·javascript
犇驫聊AI4 小时前
Chrome DevTools MCP + Claude Code 自定义skills生成接口代码生成器
前端·javascript
kyriewen5 小时前
别再这样写 async/await 了:我在 Code Review 中见过最多的 8 个错误
前端·javascript·面试
用户298698530149 小时前
在 React 中使用 JavaScript 将 Excel 转换为 SVG
前端·javascript·react.js
labixiong10 小时前
手写Promise--微任务、静态方法、async/await 全搞懂(三)
前端·javascript
铁皮饭盒11 小时前
3行代码搞定页面截图,Bun.WebView真的简单
javascript
kyriewen1 天前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
山河木马1 天前
矩阵专题2-怎么创建视图矩阵(uViewMatrix)
javascript·webgl·计算机图形学
tangdou3690986551 天前
AI真好玩系列-2分钟快速了解DeepAgents | Quick Guide to DeepAgents in 2 Minutes
前端·javascript·后端
张元清1 天前
React useIntersectionObserver Hook:懒加载与可见性检测(2026)
javascript·react.js