JavaScript 异步编程

简介: 异步编程 Async JavaScript 在 Node 面前获得前所未有的重视。本文结合 Trevor Burnham 所著 《Async JavaScript Build More Responsive Apps with Less Code(中文名: JavaScript 异步编程:设计快速响应的网络应用)》一书,梳理 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)
……未完待续……

目录
相关文章
|
1月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
15 1
|
1月前
|
前端开发 JavaScript 数据处理
在JavaScript中,什么是异步函数执行的例子
在JavaScript中,什么是异步函数执行的例子
10 0
|
1月前
|
前端开发 JavaScript
JavaScript的异步操作
JavaScript的异步操作
|
27天前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
8天前
|
Web App开发 缓存 JavaScript
|
18天前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
21 2
|
28天前
|
JavaScript 前端开发
js开发:请解释同步和异步编程的区别。
同步编程按顺序执行,易理解但可能阻塞程序,适合简单任务;异步编程不阻塞,通过回调、Promise等处理耗时操作,提高效率,适用于并发场景。开发中需根据需求选择合适方式。
11 1
|
1月前
|
JavaScript 前端开发 UED
解释 JavaScript 中的异步编程和回调函数。
解释 JavaScript 中的异步编程和回调函数。
15 0
|
2月前
|
前端开发 JavaScript UED
JavaScript中的异步编程和Promise
【2月更文挑战第3天】在Web开发中,JavaScript是一门非常重要的编程语言,而异步编程是JavaScript中的一个关键概念。本文将介绍JavaScript中的异步编程特点,以及如何使用Promise来更加优雅地处理异步操作,帮助开发者更好地理解和应用这一技术。
16 3
|
2月前
|
前端开发 JavaScript 数据处理
JavaScript中的异步编程及Promise对象
【2月更文挑战第3天】 传统的JavaScript编程模式在处理异步任务时常常会导致回调地狱和代码可读性较差的问题,而Promise对象的引入为解决这一问题提供了一种优雅的解决方案。本文将介绍JavaScript中的异步编程方式以及Promise对象的使用方法和优势,帮助读者更好地理解和运用异步编程技术。
20 8