[TOC]

摘自知乎回答:知乎问题 觉得很有启发性,故整理补充记录。

什么是JS中的异步概念

在js的语境下的异步指的是** 异步非阻塞 **的编程范式。与之相对应的是 **同步阻塞**
那么 异步/同步, 阻塞/非阻塞 是怎么区分的呢?
这两对概念,是从不同角度来描述的。
阻塞非阻塞,是根据应用层等待数据返回时的状态来区分的。当应用层调用了阻塞型I/O,那么调用之后,应用层被挂起,一直等待,直到系统内核从磁盘读完数据并返回应用层,应用层才进行接下来的操作。
而当应用层调用一个非阻塞I/O,当调用后,系统内核则会立即返回,即使这时候还没有内容数据。应用层就不会被一直挂起,可以做其他事情。
同步/异步的区别体现在系统内核获取的数据后该如何返回给应用层。对于同步型调用来说,应用层需要自己去向系统内核问询。而异步型无需应用层主动问询,而是系统内核主动通知应用层。所以同步异步却别在于消息通知的方式。

而无论异步非阻塞、同步阻塞都要需要面临一个问题:
如何编写 「一段时间后才执行」的代码?

如何执每16ms执行一段代码呢?

var lastTime = new Date()
while (true) {
  var time = new Date() // 这行每秒大概要跑几万次吧
  if (time - lastTime > 16) {
    console.log('执行')
    lastTime = time
  }
}

符合直觉,但是会死循环让单核 CPU 跑满。
C、python里,可以使用标准库的sleep函数。sleep函数封装了操作系统的能力。

while (true) {
  sleep(16)
  console.log('执行')
}

同样是死循环,但已经不会浪费 CPU 把应用卡死了。最早的JS也是这么写的。
但是对于单线程语言来说,这样问题也很大,一旦被挂起,期间其他逻辑也执行不了。鉴于js里函数是一等公民,更好的方式是:

  • 把需要的逻辑封装到一个函数里
  • 把函数交给运行时,让他在特定时间里调用

上面16ms执行一次的逻辑,现在我们知道有 setInterval 方法可以使用,

function callback () {
  console.log('执行')
}
setInterval(callback, 16)

这就的出最基础的异步非阻塞用法了,这类**交给运行时以后调用**的函数,就叫 **回调函数**
从按钮的 onclick 点击回调到 AJAX 请求的 onload 回调再到 Node.js 里的 readFile 回调,这种根基级的手法都万变不离其宗。
所以在js中,异步非阻塞以为着一种以回调函数为基础的编程范式。

JS的异步和引擎/运行时有何关系?

setTimeout 是不在ECMA-262标准里的,而是在W3C和WHATWG规范里。
纯粹的JS 引擎不支持setTimeout,因为它底层依赖操作系统(平台)的**把线程挂起**的能力。而ECMA-262的标准是独立于平台独立于宿主的。
那么JS引擎应该支持什么呢?
支持把任务 Run to Completion 就行,即完整执行从头跑到尾。
所谓任务指的是Eval一段完整的脚本或执行某个函数。可以认为浏览器加载