# 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
相关推荐
Cobyte1 小时前
19.Vue Vapor 的实现原理原来这么简单
前端·javascript·vue.js
JackieDYH1 小时前
uniapp vue3 常用的生命周期和作用使用时机
javascript·vue.js·uni-app
hhb_6182 小时前
TypeScript泛型实战:企业级请求封装全解析
javascript·ubuntu·typescript
BomanGe22 小时前
NSK直线导轨LH55EL与NH55EM替代指南
前端·javascript·数据库·经验分享·规格说明书
云水一下2 小时前
Vue.js从零到精通系列(四):前端路由与Vue Router——打造多页单页应用
前端·javascript·vue.js
研☆香2 小时前
jQuery补充知识点
前端·javascript·jquery
先吃饱再说2 小时前
JavaScript栈和队列:从“冰柜里的雪糕”到“排队打饭”
javascript·数据结构
槑有老呆2 小时前
JavaScript 数组,远不止 [] 那么简单
javascript
HjhIron2 小时前
从栈到队列,再到链表:前端开发者必知的线性数据结构
前端·javascript