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

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

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


从一段奇怪的输出说起

先看一段代码:

js

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

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

console.log('end');

我觉得输出应该是start→ 222→ end,但实际运行起来是:

sql 复制代码
start
end
222

222却跑到最后一次。


JS为什么是单线程?

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

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

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

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

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


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

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

规则很简单:

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

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

所以end在222前面先执行


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
相关推荐
鬼手点金5 小时前
nanoGPT/nanochat 能做出 DeepSeek/Qwen 这样的大模型吗?
服务器·前端·javascript·bug·openclaw
软件技术NINI5 小时前
美妆商城html+Css+js 4页
javascript·css·html
蛋蛋的就会顺顺的5 小时前
hot100 p0——栈,贪心算法,动态规划
java·前端·javascript·算法·leetcode·力扣
码艺-Alimjan6 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
DevUp9 小时前
标着 JavaScript 的 Wiki 与知识库:既不是 CMS 也不是 SSG(番外三)
javascript·node.js·cms
kyriewen9 小时前
我上手了 Vite+ 1.0:Node 不用自己装,lint 却翻车了
前端·javascript·程序员
鹏多多10 小时前
前端是如何死去的?| 完整时间线
前端·javascript·程序员
古夕11 小时前
iframe 授权失败时露出 60000 JSON 的复盘
前端·javascript
程序员Sunday11 小时前
ES Module 和 CommonJS 有什么区别?循环依赖时会发生什么?
前端·javascript·面试
有味道的男人12 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript