写作时间

2016-09-30

异步

简单地说,JavaScript 是单线程执行的语言,但在使用中有很多异步执行的情况。异步的本质是用其他方式(相对同步)控制程序的执行顺序,这与其他语言中的多线程模型不同,所以常常有人对非顺序 JavaScript 代码的运行结果感到困惑不解。

一段简单的小程序

任何使用过 JavaScript 的程序员都能说出下面这段代码的输出:

console.log("A");setTimeout(() => {console.log("B");
}, 100);console.log("C");

先后顺序是 A、C、B,因为第二个参数的作用是指定延迟的毫秒数,这段代码只有一个 setTimeout,所以不会让人迷惑。

对类似程序的解释通常是由 setTimeout 设置一个定时器,在指定毫秒数后调用回调函数。然而,它的执行机制并不是这么简单。实际上,setTimeout 的作用是在指定的毫秒数之后,在得到机会时,将 callback 放入 Event Loop Queue

Event Loop

首先要抛出一些概念,通常所说的 JavaScript Engine 是指负责执行一个一个 chunk 的程序,它依赖宿主环境的调度,也需要通过宿主环境与操作系统产生关联并得到支持。JavaScript Engine 是 JavaScript Runtime(Hosting Environment) 的一部分。

每个 chunk 通常是以 function 为单位,一个 chunk 执行完成后,才会执行下一个 chunk。下一个 chunk 是什么呢?取决于当前 Event Loop Queue 中的队首。Event Loop Queue 中存放的都是消息,每个消息关联着一个函数,JavaScript Engine 就按照队列中的消息顺序执行它们,也就是执行 chunk。

所以上面的 setTimeout 实际执行起来更接近这样:

  • chunk1执行:由 setTimeout 启动定时器(100毫秒)

  • chunk2执行:得到机会,将 callback 放入 Event Loop Queue

  • chunk3执行:此 callback 执行

不难发现,得到机会很重要!这也就可以解释用 setTimeout 延迟 1000 不一定是准确的,而是会至少延迟一秒。因为如果还有其他的任务在前面,它要等待那些任务对应的消息都出队,也就是程序都执行完成,它才能将 callback 放入队列。也就是实际延迟会大于或等于一秒。

通常所说的触发了一个事件,就是指这个 event listener 得到了执行。与 setTimeout 这个例子中的概念一样,这也是一次 chunk 的执行。像这样一个一个执行 chunk 的过程就叫 Event Loop

还有一个经常提到的概念叫「无阻塞」,JavaScript 中的无阻塞就是指这种 Event Loop 模型。除去 alert 或同步 Ajax 请求等历史原因造成的问题,程序总是不会出现阻塞;也就是说 JavaScript Engine 总是可以处理下一个任务,如处理用户对浏览器的操作。

一些简单的小例子

将 setTimeout 加入 try 语句之中,结果会如何?

try {setTimeout(() => {throw new Error("Error - from try statement");}, 0);
} catch (e) {console.error(e);
}

try catch 与 setTimeout 不在同一个 chunk,所以……你懂的。

再看下一个。

下面的堆栈信息会输出 C - B - A 吗?

setTimeout(function A(){setTimeout(function B(){setTimeout(function C(){throw new Error("Error - from function C");}, 0);}, 0);
}, 0);

它们并不对应同一条 Event Loop Queue 中的消息,分别有各自的调用栈,所以错误栈里面只有 C。

Job Queue

Job 是 ES6 中新增的概念,它与 Promise 的执行有关,可以理解为等待执行的任务;Job Queue 就是这种类型的任务的队列。JavaScript Runtime 对于 Job Queue 与 Event Loop Queue 的处理有所不同。

相同点:

  • 都用作先进先出队列

相异点:

  • 每个 JavaScript Runtime 可以有多个 Job Queue,但只有一个 Event Loop Queue

  • 当 JavaScript Engine 处理完当前 chunk 后,优先执行所有的 Job Queue,然后再处理 Event Loop Queue

ES6 中,一个 Promise 就是一个 PromiseJob,一种 Job。

再来观察一段小程序:

console.log("A");setTimeout(() => {console.log("A - setTimeout");
}, 0);new Promise((resolve) => {resolve();
})
.then(() => {return console.log("A - Promise 1");
})
.then(() => {return console.log("B - Promise 1");
});new Promise((resolve) => {resolve();
})
.then(() => {return console.log("A - Promise 2");
})
.then(() => {return console.log("B - Promise 2");
})
.then(() => {return console.log("C - Promise 2");
});console.log("AA");

在原生支持 Promise 的环境,输出是这样:

A
AA
A - Promise 1
A - Promise 2
B - Promise 1
B - Promise 2
C - Promise 2
A - setTimeout

理解这个输出:

  • A 与 AA 最先输出,因为它们不是异步任务,属于第一个 chunk。

  • Promise 1 与 Promise 2 先于 setTimeout 执行,因为 Job Queue 的执行优先于 Event Loop Queue。

  • Promise 1 与 Promise 2 各自的输出都是顺序的,因为 Job Queue 是先进先出队列,同一 Job Queue 中的任务顺序执行。

  • Promise 1 与 Promise 2 的后续任务是交错的,因为 Promise 1 与 Promise 2 都是独立的 PromiseJob(job 的其中一种),属于不同的 Job Queue,它们之间的顺序规范中没有规定。

并发

文章开头,我说「简单地说,JavaScript 是单线程执行的语言」,现在可以说得稍微复杂一点了:JavaScript Engine 对 JavaScript 程序的执行是单线程的,但是 JavaScript Runtime(整个宿主环境)并不是单线程的;而且,几乎所有的异步任务都是并发的,例如多个 Job Queue、Ajax、Timer、I/O(Node)等等。

上面说的是 JavaScript Runtime 层面,从开发者的 JavaScript 代码执行层面来说,也有一些特殊情况,例如:一个 Web Worker,是一个独立的线程,有自己的内存空间(栈、堆)以及 Event Loop Queue。要与这样的不同的线程通信,只能通过 postMessage。一次 postMessage 就是在另一个线程的 Event Loop Queue 中加入一条消息。

而一个跨域的 iframe 中,JavaScript 也有单独的内存空间(栈、堆)以及 Event Loop Queue,也只能通过 postMessage 与它通信。至于它与主页面是否运行在同一线程内,取决于浏览器的实现(目前是在同一线程内)。

参考资料

Concurrency model and Event Loop
ECMAScript® 2015 Language Specification
You Don't Know JS: Async & Performance
JavaScript异步编程:设计快速响应的网络应用

JavaScript 中的异步:Event Loop 及其他相关推荐

  1. JavaScript中的异步、同步

    要理解JS中的异步.同步,需要先了解JS代码的执行过程和Event Loop. JavaScript代码的执行过程 程序需要执行的操作都会被放入Call Stack(A LIFO (Last In, ...

  2. 技术干货 | JavaScript 之事件循环(Event Loop)

    导读:学过 JavaScript(下文简称 JS) 的都知道它是一门单线程的.非阻塞的脚本语言.单线程意味着,JS 代码在执行的任何时候,都只有一个主线程来处理所有的任务,这也就意味着 JS 无法进行 ...

  3. 浅谈,JavaScript 运行机制和Event Loop

    JavaScript是一个非常灵活的语言且是单线程 所谓是单线程就好比你去一个银行,银行只开设了一个窗口,然而一个窗口就好比一个线程,取钱的人就好比JS代码.也就是说 javascript是按照语句出 ...

  4. javascript事件轮询(event loop)详解

    英语原文摘自:http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/ The JavaScript Eve ...

  5. javascript中的异步 macrotask 和 microtask 简介

    什么是macrotask?什么是microtask? 在理解什么是macrotask?什么是microtask之前,我们先来看看javascript中的事件循环机制,先看如下面一段代码: consol ...

  6. Javascript中的异步

    在C#,Java中,异步方法,通常是伴随多线程,并发等术语一起出现的,比如C#中的async方法,是运行在一个线程池线程上,并且在异步方法运行完成后,有一个回调函数通知主线程. 那么由于Javascr ...

  7. JavaScript中的异步梳理(0)

    JavaScript中有大量异步操作,首先可以看看JS中什么东西会产生异步(这里先只考虑浏览器里的情况): Ajax(XMLHttpRequest) Image Tag,Script Tag,ifra ...

  8. javascript中的异步调用,promise对象,async/await用法

    原生javascript中的的回调函数 即callback 就是通过回调函数来通知主程序 对于io 密集的非常好用:eg. file,DB读写,网络访问 异步: javascript就是个单线程语言, ...

  9. 了解Javascript中的异步等待

    在本文中,我们将探讨async/await对于每个Javascript开发人员来说,异步编程的首选工具.如果您不熟悉javascript,请不要担心,本文将帮助您async/await从头开始理解. ...

最新文章

  1. Bulk_Collect_Performance 比较
  2. Python中使用requests和parsel爬取喜马拉雅电台音频
  3. C#字符格式化占位符
  4. 使用JQUERY实现局部页面定时刷新
  5. Reason: image not found
  6. 关于百度移动端搜索中结果聚合的几个常见案例分析
  7. autocad怎么用计算机,职称计算机AutoCAD实用技巧
  8. 广西北部湾经济区升级发展 全面对接粤港澳大湾区
  9. 移动端-安卓-接口测试简介
  10. [大数据面试]--智力题(2)
  11. 微信智能机器人助手,基于hook技术,自动聊天机器人
  12. 【汇正财经】什么是股权结构?
  13. 可变参数和Steam流的基本使用
  14. PTA 7-256 五分制成绩(函数实现)
  15. linux 在沙盒中运行,Linux容器的细粒度沙盒策略执行方法与流程
  16. JavaScript 各种参数 详解(十二)
  17. Vue源码之计算属性watcher
  18. 王者荣耀全国最低战区推荐查询,王者荣耀战区实时查询秒上省标
  19. 第一次预习作业(html,css,js简单介绍,HTML常用标签)
  20. 目录 | Flink源码走读

热门文章

  1. 开机时设置linux 内核参数 mem,Linux内核开机保留大块内存的方法总结
  2. word 产生很多temp 不显示_word表格中文字显示到最下面的时候不自动换页-解决办法...
  3. c语言实现大素数的生成,C语言实现寻找大素数
  4. python数据驱动创建账号_Django创建超级管理员账号和修改密码
  5. Eclipse中使用GIT将文件还原至上一版本
  6. 计算机维保资质,电脑维保属于技术合同吗?
  7. linux文件符数据,linux – 如何监视文件描述符以获得新数据的可用性?
  8. 怎么用百度搜索php网站,PHP简单获取网站百度搜索和搜狗搜索收录量的方法
  9. Jquery弹出层插件,非常好用绚丽Lee dialog 1.0
  10. 脉歌蓝牙耳机线评测_漂亮的高音质蓝牙耳机 脉歌MACAW TX-90评测