# 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
相关推荐
OpsEye5 小时前
大模型 Token 计量不准,是什么原因,怎么解决??
javascript·ai编程
独隅7 小时前
WebStorm Windows 版 React / Next.js 实战替换指南
javascript·windows·webstorm
冷眸_7 小时前
同名食材不能直接相减:一个 JavaScript 用量模型的边界
javascript
apollo_qwe7 小时前
接口超时了,前端为什么不能直接重试?
javascript·http
toooooop811 小时前
CRMEB 故障排查笔记:安全网关只放行 GET/POST 怎么办
前端·javascript·vue.js·thinkphp6·crmeb
软件技术NINI12 小时前
基于SpringBoot和Vue的项目评审系统
javascript·vue.js·spring boot·后端·spring·毕业设计·毕设
vipxieliang12 小时前
JavaScript 模板字符串与标签模板:不只是拼接
前端·javascript
Dream-Y.ocean1 天前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Highcharts.js1 天前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
kyriewen1 天前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员