JS深入理解异步

什么是异步

JS 为何会有异步

JS 是单线程的语言,所谓“单线程”就是一根筋,对于拿到的程序,一行一行的执行,上面的执行为完成,就傻傻的等着。例如:

1
2
3
let i, t = Date.now();
for (i = 0; i < 100000000; i++) {}
console.log(Date.now() - t) // 261 (chrome浏览器)

上面的程序花费 261ms 的时间执行完成,执行过程中就会有卡顿,其他的事儿就先撂一边不管了。

执行程序这样没有问题,但是对于 JS 最初使用的环境 ———— 浏览器客户端 ———— 就不一样了。因此在浏览器端运行的 js ,可能会有大量的网络请求,而一个网络资源啥时候返回,这个时间是不可预估的。这种情况也要傻傻的等着、卡顿着、啥都不做吗?———— 那肯定不行。

因此,JS 对于这种场景就设计了异步 ———— 即,发起一个网络请求,就先不管这边了,先干其他事儿,网络请求啥时候返回结果,到时候再说。这样就能保证一个网页的流程运行。

异步的实现原理

1
2
3
4
5
6
let ajax = $.ajax({
url: '/data/data1.json',
success: function () {
console.log('success')
}
})

上面代码中$.ajax()需要传入两个参数进去,urlsuccess,其中url是请求的路由,success是一个函数。这个函数传递过去不会立即执行,而是等着请求成功之后才能执行。对于这种传递过去不执行,等出来结果之后再执行的函数,叫做callback,即回调函数,所谓”回调函数”(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

再看一段更加能说明回调函数的 nodejs 代码。和上面代码基本一样,唯一区别就是:上面代码时网络请求,而下面代码时 IO 操作。

1
2
3
4
let fs = require('fs')
fs.readFile('data1.json', (err, data) => {
console.log(data.toString())
})

从上面两个 demo 看来,实现异步的最核心原理,就是将callback作为参数传递给异步执行函数,当有结果返回之后再触发 callback执行,就是如此简单!

常用的异步操作

  • 网络请求,如ajax http.get
  • IO 操作,如readFile readdir
  • 定时函数,如setTimeout setInterval

事件绑定是不是也是异步操作

异步和 event-loop

JavaScript 运行机制详解:再谈Event Loop

这一次,彻底弄懂 JavaScript 执行机制

事件绑定算不算异步?

事件绑定和异步操作的实现机制是一样的,那么事件绑定是不是就是异步操作呢?(声明一下,这里说的事件绑定是如下代码的形式)

1
2
3
$btn.on('click', function (e) {
console.log('你点击了按钮')
})

相同

从技术实现以及书写方法上来讲,他们是一样的。例如事件绑定和 IO 操作的写法基本相同。

1
2
3
4
5
6
$btn.on('click', function (e) {
console.log('你点击了按钮')
})
fs.readFile('data1.json', function (err, data) {
// 获取数据
})

最终执行的方式也基本一样,都会被放在 call-stack 中通过 event-loop 来调用。

不同

第一,event-loop 执行时,调用的源不一样。异步操作是系统自动调用,无论是setTimeout时间到了还是$.ajax请求返回了,系统会自动调用。而事件绑定就需要用户手动触发

第二,从设计上来将,事件绑定有着明显的“订阅-发布”的设计模式,而异步操作却没有。

其实,事件绑定在 js 中扮演着非常重要的角色,各个地方都会用到事件绑定的形式。例如 web 页面监控鼠标、键盘,以及 nodejs 中的 EventEmitter 应用非常广泛(特别是涉及到数据流时)。事件绑定被应用到非常广泛,却没有发生像异步操作带来的程序逻辑问题。