JavaScript 异步编程

异步编程 Async JavaScript 在 Node 面前获得前所未有的重视。本文结合 Trevor Burnham 所著 《Async JavaScript Build More Responsive Apps with Less Code(中文名: JavaScript 异步编程:设计快速响应的网络应用)》一书,梳理 JavaScript 的异步编程的方方面面。

为更好地了解异步开发的来龙去脉,我们先回顾一下 JS 服务端的历史,看到底解决了什么问题(当然是否真的解决是另外一个问题),并以此来与 Node 横向比较。

  • 早期出现的 Javascript 服务端:Netscape LiveWire,它将阻塞式 I/O包含在内,使用多进程模
  • 上世纪九十年代中期出现的 ASP,微软很给力,除了主流的 VBS 编程模式,还提供有基于 JScript 的。此时期到后来的 Aptana 的 Jaxer 也是没有突破旧的同步模式
  • 2009 年,Node 问世了,其特点:异步、非阻塞、高并发。这里说明一下,“异步”、“非阻塞”、“高并发”三者之间的关系可以说是一层一层递进的

实现异步的关键,简单讲,在于“闭包(Closures)”。函数是 JS 为第一等公民,可以把函数作为对象调来调去,并一个函数轻易地包含另外一个函数;或者被另外一个函数包含着,也没有问题。——故所以,在 JS 里面闭包是天然支持的。从泛语言的角度讲,任何有闭包的语言都是函数式语言,区别在于写起来是否轻松,越轻松的话那么越名副其实。这样的话,JS 具备了那么多优点,如果把 JS 应用在非阻塞环境,例如 Socket 网络编程中,是否可行呢? Node 作者 Ryan Dahl 就是这样的想的,于是尝试将 V8 与 非阻塞的 C 代码结合起来,从而诞生了 Node。后来事实证明,Ryan 的方式不仅可行且表现不俗。

综合几个方面,我们不妨再思考下这些问题:

  1. 异步编程、事件模型、事件队列、阻塞/非阻塞、异步函数/回调函数……等等,这一堆名词有神马区别与联系?假设我们要以此进行苦逼的考试,这势必将是一堆名称解析的题型:(
  2. 如果我们是从前端过来的,怎么发现前端没有“大谈特谈”异步编程,这又是为何?
  3. 到底 JS 怎么实现异步编程?最朴素的 JS 底层机制究竟是怎样的?
  4. 更重要的,我的 异步 JS 应该怎么写法好?

实际上,包括 JAVA 在内的许多 API 如 JDK 都提供 NIO 非阻塞版本之接口。如果使用多线程模型编码,面临着若干问题。先着眼于两点:

  • 线程是贵重的。在一定环境下,使用线程的数量是有限制的。
  • 为保证数据的线程安全性,需要调用互斥量或者信号量来封装数据。这将增加我们代码的复杂性。

多线程模型下固然同样可以处理非阻塞调度,只是相比事件模型消耗的资源来得更大,尤其长连接的场景,最能体现这种不足。另外编码成本也更高。关于两者的分析,小弟在旧文已经探讨过,详见《学习NodeJS第二天:漫谈NodeJS 》。

前端没有“大谈特谈”异步编程

实际上,不论前端抑或后端,都会遭遇一个问题,就是如何优雅应对复杂事件集的范畴,这仍属于 JavaScript 有待解决的前沿领域。

历史与当前总结

从1995 年诞生的年份开始起,本属“草根”到不得了的 JavaScript 于 AJAX 革命成功之后“颠覆性”地一路走来并在茁壮地成长,除了 VB Script 正面较量外,其他的 RIA 应用还不算有真正的威胁,Flash、Sliverlight、Java Fx 你方唱罢我登台,对 JavaScript 倒也可算“小打小闹”。

不管怎么样,JavaScript 就坚守在浏览器的阵地。Google Gmail 之于 JavaScript 所倚重的力量有目共睹,于是数以百计的项目纷纷把 JavaScript 派上前端。这一热潮更是催生了 JS Runtime 之竞争态势,Apple 的 Safari/Webkit、Mozilla 的 Firefox OS、连微软的 Metro 界面开发都把 HTML5 置于与 C#、VB 平起平坐的地位。

若说语言有生涯,这便是 JavaScript 生涯中的第一个转折点。

渐渐地,JavaScript 成为一门体面的语言。这固然与原发明者出色的设计理念有关;更重要的是,拜无处不在的浏览器所赐——JavaScript 比任何语言都有资格兑现了 Java 那古老的承诺“一次编写、到处运行”。

然而,没有下一站“给力”的开发潮流,恐怕一切美好都是“虚火”,不足以让人们把视野关注在 JavaScript 身上。如果把 JavaScript 比作一个男人,他会有第二个转折点吗?

不如将 JavaScript 真正可应用于服务端编程,岂不是更好!?这种全端的开发模式不用说也是顺理成章的。

——恰好,Node 出现了。

关于 Node 本身的优点已经铺天盖地了,不想多分析,但要探讨的是,Node 之出现对于 JavaScript 的“利导”不见得也是一帆风顺的。

JavaScript 设计的初衷是为了强化 Netscape 浏览器的展现能力,仅仅是脚本之目的。不曾想,现在业已成为多媒体、多任务、多内核网络世界中一员,然而微妙的却是,JavaScript 并没有摇身一变成为支持多线程的语言(也许随着语言规范的发展也会加入),而是稳固单线程的一门语言(早已超出脚本,可称为语言了,或者界限已经模糊了)。

下面我们花大量篇幅来介绍扫服务端事件驱动开发的概念。理解这些概念是实践 JS 异步编程的关键。

关于 JS 的一些认识

在许多语言中,事件模型不属于语言级别的支援,而是由外层 API 提供。但 JS 事件一直是语言的核心;

前面已经提到,JS 对闭包天然的支持。这姑且不以晦涩的闭包概念深入原理,只是明白

引入线程的概念是为了“并行”,可以处理多个任务同时开始,同时执行,同时进行,从而整体上加快最终效率。多个线程对应多项任务,多个对多个的分工这很好理解。但我们知道, Node JS 始终是单线程程序,却怎么运行多个任务,而且效率反而高?——这怎么说?实际上,我要告诉大家三点,1)JS 的确同一时间内只会做一件事,这是所谓单线程的表现;2)那岂不是同步意思了,难道不能并行了吗?但没关系,且看;3)我们把 JS 设计为一个“圈 Loop”,一个可以永远(当然也可以手工或者强行中止的)运行下去的循环,同时这个循环结构上是个队列,允许你不断往这个队列加入新任务。如果发现处理完毕的事件,则从队列中剔除表示执行完毕;如果没处理完毕,嗯~这个循环看了看之后不做什么,继续走下去不停留。又因为是循环的缘故,尚未完成的任务又会被事件机制访问,直到执行完毕为止。如此便可以把多个任务“不落单”地处理完毕。

是不是到这里,问题就完了?不对~感觉多任务执行如何哪里,你始终还没有说清楚,对不对?

嗯,能够提出深入的发问很好。尽管我没有挖据 V8 & Linux 代码去论证,但相信凭借我的自圆自说,个中的原理是这样的:

  • JS 再一次成为了接口语言,在 Node V8 里面封装了 C/C++ 底层,呈现一套以 JS 语法的 API。但实际运算仍交到 C/C++ 执行;
  • 假设这些任务都需要一定的耗时来完成,也就是说,使用异步的场景是适合的;
  • 使用 Node 不等于你程序跑起来就是纯粹的单线程程序,得从微观角度观察;
  • 因为 JS 不允许同时多个任务,但底层的 C/C++ 可以,于是底层运行着的是多个线程,因此多个任务同时运行成为可能。
  • 事件队列中既可以是 JS 闭包,也是可以是系统的调用。

虽然 JS 初始化了底层去执行任务,但 JS 并不干涉任务的过程,不关心任务怎样完成,他只需要知道最终结果,ok (触发用户成功的回调)还是不 ok(触发 err 事件)。JS 知不知道这些多个线程存在?当然知道,但决不会把它们暴露出来,而是自己“事件循环”的机制来调度。你可以把 JS 这一层面想象为一个指挥者、总的调度者,它催生了多个任务同时跑,然后经常不辞劳苦地围着任务列表(即“事件队列”)在转,一个一个挨着问,“搞定没有”?“没有吗?”,“没有,在弄呢”,“好,我不催你”,继续访问下家……周而复始。这个过程中,系统事件内部是有多线程在跑的。只是我们不晓得而已,我们看到的只是 JS 层面的机制。这固然是 Node 优雅的地方,也是其卖点所在:不用线程却能操控多任务。也许不了解的人以为这是 JS 的“魔法”所赐,但无论如何形容,我们的任务就是要摸清楚这套 JS 机制,为我所用。

若要以线程称呼 JS,那么 JS 便是单线程程序。但有没有多线程的 JS?有!更确切地说,你可以在你程序中让多线程参与进来,但 JS 基本单元就只是一条线程。未来 JS 语言规范会出现线程处理的关键字,不是没有这种可能。但现在 JS 单线程模型是简单的、朴素的、友好的。当然有许多方式为你的 JS 程序提供多线程的支持。下面我们也分别进行介绍。假设不借助其他手段,一个 JS 程序有且只有一个事件循环队列。

var start = new Date;
setTimeout(function(){
    var end = new Date;
    console.log('Gone:', end -start, 'ms');
}, 200);
// alert(11)
while((new Date - start) < 1000){} // 强行阻塞
// alert(33)

……未完待续……

时间: 2024-10-24 06:50:52

JavaScript 异步编程的相关文章

探索Javascript异步编程

异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显.许多不同的方法都可以解决这个问题,本文讨论了一些方法,但并不深入.大家需要根据自己的情况选择一个适于自己的方法. 笔者在之前的一片博客中简单的讨论了Python和Javascript的异同,其实作为一种编程语言Javascript的异步编程是一个非常值得讨论的有趣话题. JavaScript 异步编程简介 回调函数和异步执行 所谓的异步指的是函数的调

javascript异步编程和单线程之间的一些疑惑

问题描述 javascript异步编程和单线程之间的一些疑惑 这两天在研究js异步编程机制,查阅了很多资料,中文的英文的都有.大致上对js异步实现有些了解.但有几点不太理解. 第一个问题: 有一点无论是国内的资料还是英文的,都没有讲到到(可能是过于基础,本人是业余前端,对线程这一块不是很了解).我们说javascript是单线程,即一次只能执行一个任务,前一个任务结束,后一个才会执行.这是从抽象后的javascript的语言层面说的,还是指各种编译环境内部本来就是单线程? 比如当Thread P

JavaScript异步编程解决方案

最近看了一些javascript异步编程方面文章, 也反复读了几遍薄薄的 << Async JavaScript >>.总结一下, 供自己后续学习使用, 并分享给大家. 首先, 有几个问题: 什么是异步编程/异步函数? 异步函数和回调函数有什么关系? 为什么异步编程经常与javascript同时出现? javascript中的异步函数的机制是怎样的? 那么现在异步编程有什么解决文案? 未来的javascript异步编程是什么样子? 什么是异步函数? 对一个jser而言,学习和使用j

详解JavaScript异步编程中jQuery的promise对象的作用_jquery

Promise, 中文可以理解为愿望,代表单个操作完成的最终结果.一个Promise拥有三种状态:分别是unfulfilled(未满足的).fulfilled(满足的).failed(失败的),fulfilled状态和failed状态都可以被监听.一个愿望可以从未满足状态变为满足或者失败状态,一旦一个愿望处于满足或者失败状态,其状态将不可再变化.这种"不可改变"的特性对于一个Promise来说非常的重要,它可以避免Promise的状态监听器修改一个Promise的状态导致别的监听器的行

javascript异步编程_javascript技巧

setTimeout (slow, takes about 10 sec) function async(callback) { setTimeout(callback, 0); } img.onerror (data:uri) function async(callback) { var img = new Image; img.addEventListener('error', callback, false); img.src = 'data:,foo'; } script.onready

再谈JavaScript异步编程_javascript技巧

随着前端的发展,异步这个词真是越来越常见了.假设我们现在有这么一个异步任务: 向服务器发起数次请求,每次请求的结果作为下次请求的参数. 来看看我们都有哪些处理方法: Callbacks 最先想到也是最常用的便是回调函数了,我们来进行简单的封装: let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result =

理解javascript异步编程_javascript技巧

一.异步机制 JavaScript的执行环境是单线程的,单线程的好处是执行环境简单,不用去考虑诸如资源同步,死锁等多线程阻塞式编程等所需要面对的恼人的问题.但带来的坏处是当一个任务执行时间较长时,后面的任务会等待很长时间.在浏览器端就会出现浏览器假死,鼠标无法响应等情况.所以在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应.所谓异步执行,不同于同步执行(程序的执行顺序与任务的排列顺序是一致的.同步的),每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一

详谈javascript异步编程_javascript技巧

异步编程带来的问题在客户端Javascript中并不明显,但随着服务器端Javascript越来越广的被使用,大量的异步IO操作使得该问题变得明显.许多不同的方法都可以解决这个问题,本文讨论了一些方法,但并不深入.大家需要根据自己的情况选择一个适于自己的方法. 本文为大家详细介绍js中的异步编程,具体内容如下 一 关于事件的异步 事件是JavaScript中最重要的一个特征,nodejs就是利用js这一异步而设计出来的.所以这里讲一下事件机制. 在一个js文件中,如果要运行某一个函数,有2中手段

Javascript异步编程的4种方法

你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯:坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏 览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其