1 min read

JavaScript 的异步、单线程和队列

这是我第一次明白 JavaScript 的异步、单线程、队列这几个概念,感谢 Async JavaScript 一书。

异步计时函数

首先从异步计时函数 setTimeout 说起:

var begin = new Date();//代码开始
setTimeout(function(){
    var end = new Date();
    alert('陈三你好,这个程序已经运行了' + (end - begin) + '毫秒');
},1000);//1秒后弹出对话框
while ((new Date() - begin) < 2000) {}//循环代码持续2秒

在 firefox 下,按 Shift + F4 调出代码片段速记器,将以上代码拷入,然后 Ctrl - R 运行。

我的某次数值是 2019。

这个数值让我大吃一惊,不是说好 1 秒后弹对话框的吗?

如果我们是在多线程程序语言下,那我们的期望是对的。但 JavaScript 是单线程,这就与我们的期望有出入。

单线程是什么意思?

JavaScript 从 var begin = new Date(); 执行起,然后解析 setTimout 函数。setTimout 一段代码表示,嘿,请在 1 秒后,把我加入事件队列里。之后回调函数排队等候。

JavaScript 继续运行下一句 – 在这个例子中是 while 循环。这个循环要持续2秒。这期间,回调函数继续在排队。

这就是单线程设计下的 JavaScript。队列里的回调函数不会主动发问,它只是被动等待。等 JavaScript 忙完最后一句代码,JavaScript 虚拟机就会朝队列喊话:下一个。

这时,我们的 alert 终于可以运行了 – 但时间已经过去 2019 毫秒。

异步 I/O 函数

现代常用的 Ajax 操作属于异步 I/O 类型,比如 GET、POST。

$.get('http://www.zfanw.com/blog/',function(data){//因为 JavaScript 的同源策略,本地执行本语句其实会失败,仅做示例
        alert(data);
    });
while (true) {}

JavaScript 在执行到 get 语句时,发起 HTTP 请求,要求返回 http://www.zfanw.com/blog/ 页面,并且定义一个回调函数,将回调加入事件队列,以便 HTTP 响应成功时接收数据,然后 JavaScript 继续执行下一句,即 while,因为 while 条件一直为真,所以程序将永远运行下去,此前定义的回调函数一直在排队,页面呈假死状态。

当然,这个例子有点极端,举一个轻松点的,本地服务器上架设的例子:

$.get('index.html',function(data){
        alert(data);
    });
alert('hey Chen');

代码执行到 ‘hey Chen’ 一句,浏览器弹出对话框窗口。

且慢关掉对话框窗口,打开 Google Chrome 开发者工具查看 Network 选项卡,get 语句的 Status Text 字段正处在 Pending 状态中。chrome 开发者工具帮助上并没有说明这个词的意思,但我们可以借助 WireSharkFiddler2 检查 HTTP 请求的状况:

wireshark 抓取 http 数据

上图中可以看到,请求 – 响应过程是成功的,只是 get 定义的回调未能执行,所以 Chrome 下显示成 Pending 状态。现在我们按下对话框窗口的确定按钮,又或关掉对话框,回调函数就执行了。

以上是 Google Chrome 30.0.1599.114 版本下的情况,即 alert 方法会阻止事件循环,导致回调无法接收数据。

Firefox 24 下的情况略有不同,示例3中的 alert 并不能阻止事件循环,两个 ajax 请求全部执行并且回调也执行了,这样页面上会有三个弹出对话框。

WireShark 抓取的 Firefox 下 HTTP 数据如图:

wireshark log firefox http

当然,浏览器的处理方法不同这种事情实在不值得我大惊小怪,毕竟,我是从 IE6 混战里活过来的(笑。

扩展阅读

  1. Introduction to HTML5 Web Workers
  2. How does Asynchronous Javascript Execution happen
报告问题 修订

如果你有自建 https 代理的需求,欢迎尝试 Phantom,一键搭建,方便快捷。查看 demo