自JavaScript诞生之日起,频繁与异步打交道便是这门语言的使命,并为此衍生出了许多设计和理念。因此,深入理解异步的概念对于前端工程师来说极为重要。

什么是异步?

程序是分“块”执行的。最常见的“块”是函数。在一个块内,语句大体上从上到下依次执行(除了“声明提升”等个别例外,但那是发生在编译阶段),这些语句便是同步代码;而有一些语句,即所谓的回调函数,并不会按照“正常的”顺序执行,而是会在将来的某一时刻被调用执行,这部分语句便是异步代码。

也就是说,同步和异步的本质区别在于:一个现在执行,一个将来执行。此处的“现在”和“将来”与时间长短无关,而是指“是否在同一个块内执行”。

举个例子:

// **同步**
var data = 0;
// getData是某种阻塞式的IO操作
data = getData(); // IO操作耗时10秒
console.log(data); // 语句A
// **异步**
var data = 0;
// ajax是某个库函数
ajax('www.someurl.com', function(res) {
data = res;
console.log(data); // 语句B
}); // ajax操作耗时1秒
console.log(data); // 语句C

其中语句A、C是同步代码,而B位于异步块内,也就是说,“同步”部分的三条语句在同一个块内执行,而“异步”部分的代码被分成了两个执行块。如果把这两段代码放在两个浏览器里,同时开始执行,则这三条语句从时间线上看完成的顺序是:C(几乎立即)->B(1秒后)->A(10秒后),可见同步、异步的概念与时间上的先后没有必然关系。

想象如下场景,一群代码语句正依次排队进入一家名叫“CPU”的电影院看电影,突然,其中一条语句被检票员“JS引擎”拦下了,检票员对该语句说:“你是异步代码,不属于这个场次,请到休息区等候。请放心,我们已经记住你了,到了你的场次我们会通知的。”

“那我该看哪场电影呢?”

“这个不一定,有可能下一场就轮到你,也有可能等好几场。”

“那我要等多久呢?”

“这也不一定,等候时间从无限趋近于0到正无穷大都有可能。”

“……”

语句B所在的回调函数就是那个被拦下的苦命的异步代码。

JS中的异步

本文开头说,JS是为异步而生的。为什么这么说呢?

首先,JS是为了给网页提供用户交互功能而发明的脚本语言,而用户行为是无法预知的,页面只能在用户动作时被动响应,因此响应代码必然是异步执行的。

另外,自从发明了ajax,JS还承担了网络通信的任务,这是一种时间无法预知的IO操作,而为了不阻塞用户界面,IO也必须做成异步的。

上面说了两个异步场景,加上第三个,便构成了JS的三大异步来源:用户交互IO定时器

定时器很有意思,因为它是唯一一个创建“自我控制”的异步代码的方式,可以大致控制异步代码被调用的时机(但无法精确控制)。看如下代码:

// **定时器**
var data = 0;
// 将时延设为0
setTimeout(function() {
data = 1;
console.log(data); // 语句A
}, 0);
console.log(data); // 语句B

试问,A和B谁先执行?

答案是B,因为A是异步代码嘛,必须等到“下一场”才能执行。

那么下一个问题来了,JS是如何实现异步调度的?

事件队列

答案就是事件队列。接触过前端的人应该或多或少知道这个概念。

JS引擎(如Chrome的V8)本身只负责编译和执行宿主环境“喂”给它的语句,事件队列由宿主环境提供。当引擎发现回调代码时,它便通知宿主环境在“适当”的时候来调用该回调。但是既然是异步代码,那就存在一个问题:可能同一个时间有多个回调被触发,也有可能一个回调正在执行时另一个回调被触发了。JS又是单线程,不能并行处理多个回调。这种情况下怎么办呢?学过操作系统的我们毫不犹豫作答:弄一个队列,FIFO啊!于是就有了事件队列。队列里的其实并不是“事件”,而是一个个异步代码块(回调函数)。每当一个“事件”(不只是DOM事件)被触发,相应的回调函数便被加入队列,等待被依次调用。调用一个回调函数的过程被称为一个tick。

回顾上一部分的代码,setTimeout实质上是在指定时延之后把回调函数加入事件队列的队尾,因此即使时延为0,也一定比当前tick更晚执行,而且开始执行的时间往往会稍晚于设定的时延。

有一个地方值得注意:没有任何办法“插队”,也无法打断当前tick的运行。因为在这种机制下,对回调函数的调度,如何时被调用、调用几次等,都掌握在宿主环境的手里,它不提供API的话,JS本身没有办法影响。

更进一步思考,发现JS的三大异步来源(用户交互:DOM;IO:AJAX;定时器:无标准,浏览器厂商自己搞的??)都不是ECMAScript标准的势力范围!这意味着什么呢?细思恐极……直到有一天,随着ES6的发布,Promise闪亮登场,标志着ECMAScript开始设法争夺对异步的控制权,前端开发进入了一个崭新的时代……

推荐阅读:《你不知道的JavaScript·中卷》第二部分:异步和性能

漫话JavaScript与异步·第一话——异步:何处惹尘埃的更多相关文章

  1. 漫话JavaScript与异步·第二话——Promise:一诺千金

    一.难以掌控的回调 我在第一话中介绍了异步的概念.事件循环.以及JS编程中可能的3种异步情况(用户交互.I/O.定时器).在编写异步操作代码时,最直接.也是每个JSer最先接触的写法一定是回调函数(c ...

  2. 漫话JavaScript与异步·第三话——Generator:化异步为同步

    一.Promise并非完美 我在上一话中介绍了Promise,这种模式增强了事件订阅机制,很好地解决了控制反转带来的信任问题.硬编码回调执行顺序造成的"回调金字塔"问题,无疑大大提 ...

  3. 让你高效的理解JavaScript中的同步、异步和事件循环

    "同步请求","异步请求"相信这两词在程序猿的世界中频频出现,到底是词性的妖娆,还是撸代码的基础要求,下面直接分享本人学习的好东西,保证让你深入浅出,爽得不要不 ...

  4. JavaScript 学习笔记之线程异步模型

    核心的javascript程序语言并没有包含任何的线程机制,客户端javascript程序也没有任何关于线程的定义,事件驱动模式下的javascript语言并不能实现同时执行,即不能同时执行两个及以上 ...

  5. Javascript异步编程之一异步原理

    本系列的例子主要针对node.js环境,但浏览器端的原理应该也是类似的. 本人也是Javascript新手,把自己这段时间学习积累的要点总结下来,希望可以对同样在学习Javascript/node.j ...

  6. JavaScript有同步任务和异步任务,浏览器是怎么处理的?

    1.在讨论浏览器与JavaScript之前,我们先来简单了解一下进程与线程 进程(process):资源分配的最小单位 进程是应用程序的执行实例,是操作系统进行资源分配和调度的一个独立单位. 线程(t ...

  7. [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序

    [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序 本节导读: 本节主要说明使用异步进行程序设计的优缺点及如何通过异步编程. 使用 ...

  8. C#“同步调用”、“异步调用”、“异步回调”

    本文将主要通过“同步调用”.“异步调用”.“异步回调”三个示例来讲解在用委托执行同一个“加法类”的时候的的区别和利弊. 首先,通过代码定义一个委托和下面三个示例将要调用的方法: ); //模拟该方法运 ...

  9. Linux设备驱动中的异步通知与异步I/O

    异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...

随机推荐

  1. oracle dblink使用

    一.dblink介绍 dblink是一个单向的数据库连接,通过设置可以访问其他数据库中的数据跟使用本地数据库中数据一样的感觉,但注意是单向的dblink.例如:A建连B的dblink,A可以访问B的表 ...

  2. bzoj 1875 [SDOI2009]HH去散步(矩乘)

    Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又因 ...

  3. 教程-EhLib70的安装方法

    1.下载EhLib 5.2包2.将Common文件下的文件全部复制到DELPHI7目录下.3.打开DELPHI7.0程序:打开DclEhLib70.dpk文件包点“编译”->“安装”打开EhLi ...

  4. android图片闪烁或帧动画

    remote_recording_transition.xml 文件 <?xml version="1.0" encoding="utf-8"?> ...

  5. void,extern,sizeof

    高手潜规则:禁用goto 程序质量与goto出现次数成反比 void指针的意义 1.C语言规定只有相同类型的指针才可以相互赋值 2.void*指针作为坐值用于"接收"任意类型的指针 ...

  6. 模拟等待事件row lock waits

    是索引块分裂引起的锁等待,往往与enq: TX - index contention 伴随产生,enq:TX - index contention的解释. Waits for TX in mode 4 ...

  7. C# DataTable的詳細使用方法

    在项目中经经常使用到DataTable,假设DataTable使用得当,不仅能使程序简洁有用,并且可以提高性能,达到事半功倍的效果,现对DataTable的使用技巧进行一下总结. 一.DataTabl ...

  8. Java调用yahoo!API获取天气数据

    先把代码复制上来,以后再做补充 package com.weather.test; import java.io.InputStream; import java.net.URL; import ja ...

  9. AIDL通信原理

    AIDL (Android Interface Definition Language),通过定义通信接口来实现进程间通信.这是Google提供的一种在安卓应用进程间通信的工具.所以要了解AIDL的通 ...

  10. 微信公众号支付(三):页面调用微信支付JS并完成支付

    一.调用微信的JS文件 1.首先要绑定[JS接口安全域名],“公众号设置”的“功能设置”中 2.引入JS文件 备注:支持使用 AMD/CMD 标准模块加载方法加载 <script type=&q ...