JavaScript 中的异步
什么是异步?
JavaScript 引擎是单线程的,这就意味着同一时间引擎自身只能做一件事,如果有很多事情要做,就必须一件一件来,在 JavaScript 中如果前面的某个任务需要耗费很长时间,后面的任务就被阻塞了,同时也无法响应用户的操作,例如 click 事件,看起来就像浏览器卡住了。
如果我们在浏览器中要通过 API 向服务器请求数据,就需要使用 XMLHttpRequest 对象发送一个 Ajax 请求,同时还会监听 HTTP 响应事件并指定一个回调函数来拿到这个数据,如果这个请求是同步的,那么在收到 HTTP 响应之前 JS 引擎就会一直处于阻塞状态,无法执行后面的代码也无法响应用户的交互操作。
如果是异步的就不会通过阻塞 JS 引擎的方式来等待 HTTP 响应,JS 引擎会告诉宿主环境(浏览器或者 Node)在收到 HTTP 响应之后将回调函数插入事件循环队列的末尾,然后自己会继续执行后面的代码,在未来的某个时间点,宿主环境收到这个 HTTP 响应之后就会将回调函数插入事件循环队列的末尾,在事件循环队列里的回调函数最终都会被 JS 引擎按顺序一一执行。
事件循环 (Event Loop)
在 You Don't Know JS 中有一段代码可以很形象的表现出事件循环的基本模型
// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = []
var event
// keep going "forever"
while (true) {
// perform a "tick"
if (eventLoop.length > 0) {
// get the next event in the queue
event = eventLoop.shift()
// now, execute the next event
try {
event()
} catch (err) {
reportError(err)
}
}
}
可以这么理解,JS 引擎在执行完同步代码之后(或者说 call stack 变空后)就会执行上面这个 while 死循环,程序初始化之后触发的所有操作都会完成后将对应的回调函数 push 到事件循环队列里,JS 引擎会一个接一个按顺序取出队列里的回调函数执行它。
有哪些操作是异步的?
通常有以下几种情况会触发异步操作:
- setTimeout
- setInterval
- Promise
- XMLHttpRequest
- 事件处理器
最简单的一个例子:
setTimeout(() => console.log(2), 0)
console.log(1)
第一行表示立即将 setTimeout 的第一个参数添加到任务队列末尾,接着执行第二行, 最后才会执行任务队列中的任务,最终会打印出 1 2
需要注意的是,setTimeout 和 setInterval 在执行时间上是不可靠的,setTimeout 表示在指定的延迟后将第一个参数添加到任务队列末尾,setInterval 表示每隔指定的时间就将第一个参数添加到任务队列末尾。
setTimeout(() => console.log(2), 0)
task() // 假设这个函数会耗时 10 秒
上面的代码在开始执行时就会立即将 setTimeout 的第一个参数添加的任务队列末尾,但是在 10 秒后才会打印出...
剩余内容已隐藏