本人也是Javascript新手,把本身这段时间学习积累的要点总结下来,但愿能够对一样在学习Javascript/node.js的同窗有一些参考价值。尽可能用通俗的语言帮助你们理解,若是有描述或理解不许确的地方欢迎你们指正,交流。另外本文假定你已经对javascript的语法和异步有一些基本的概念。
本系列会按通常学习异步编程的顺序,首先介绍一下异步的原理,而后介绍各类异步编程的方法,从回调函数开始,而后慢慢进入Promise和Generator等对异步编程体验进行改进的技术。期间也会大概提一下事件监听方式的异步调用,可是由于太多的事件监听会让程序流程变的不清晰,因此不太推荐(其实事件模式本质上也是回调函数)。Promise和Generator才是到目前为止(ES6),最好的异步编程方式。后面也会分享一下本身所理解的这两种方式的对比(网上也有不少争论)。ES7提出了更好的改进方案,但愿很快能够抽出时间研究一下,这是后话:)
好,进入本节的正文。
什么是异步?同步异步与阻塞非阻塞有什么关系?
node.js的“一切皆异步”的思想颇有创意,目的是可让开发者轻松编写高性能的web服务端,而不会“不当心”就用同步api阻塞了服务器从而影响性能。其余的语言好比php, python, java等基于同步的语言,虽然也有异步api,但毕竟编程人员的“思想上是同步的”,有时候不可避免的会写出阻塞的代码,node.js的目标是造就“思想上是彻底异步”的编程人员和编程语言:)
异步跟同步最大的不一样就是异步api或函数被“调用”后不会等它运行结束再执行它后面的代码,而是调用以后直接往下执行,异步函数的“执行”其实是放在“其余地方”,待“执行”完成后再把结果经过回调函数来进行进一步的使用或处理(因此异步函数书写的时候不要用"return"来返回值哦,必须经过回调函数来返回值)。这里为何强调“调用”和“执行”两个词呢?就是为了更好的理解异步的过程。
打个比喻,你的领导要作个电子报表,笨领导的作法是,在本身电脑上把该统计的统计了,该算的算了,最后生成一张报表,而后用这个报表作接下去的工做;聪明的领导,会发个邮件给下属,把报表的要求写清楚,下属在本身的电脑上把报表作完后,发个邮件把报表交回给领导。在下属作报表的时候,领导能够在本身电脑上继续作其余事情,好比玩游戏、看视频等等(你懂的)。
在上面这个例子中,领导是编程者(你),领导的电脑是当前线程,下属的电脑是另外一个线程(若是有多个下属就至关于有个线程池)。作报表这件工做是个异步函数,发邮件给下属至关于调用这个函数,下属电脑上作报表至关于在另外一个线程异步执行这个函数,执行完了发邮件把报表发回给领导至关于调用回调函数,领导就可使用这个报表接着作下面的工做(至关于回掉函数里面的代码)。下属作报表的时候领导彻底不用管而是能够继续干其余事情。
经过这个例子能够清楚的看到,领导只能在他本身的电脑(用户线程)上工做,异步的函数都是在下属的电脑上(异步线程)作的。这一点在不少文章当中并无讲的很清楚,因此容易形成困扰,由于不少人只是一味的强调javascript是单线程的,但单线程怎么能实现异步呢?就并无讲清楚。其实所谓的单线程是指用户线程是单线程,而另外还有一个或多个线程处理异步代码的执行。
接下来再说说阻塞的问题。不少文章里面在讲解的时候同步异步阻塞非阻塞混为一团,新手很难理解,最容易产生的误解就是同步=阻塞,异步=非阻塞。其实阻塞非阻塞跟同步异步没有任何关系。简单讲,阻塞就是一个api或者函数运行时间过长,而独占cpu致使其余代码不能运行,那么多长时间算阻塞呢?相信这只是一个相对的概念,只要明显影响到程序的性能和用户体验,就算阻塞吧。那跟同步异步是什么关系呢?阻塞的代码若是同步执行就会阻塞到本身后面代码的运行,因此天然而然的就想到异步来执行阻塞的代码,然而异步真能解决阻塞问题吗?下面继续讲。
node.js里面的异步
你们知道Javascript的基本语法跟其余编程语言大同小异,最大的不一样就是把异步摆在首位,特别是node.js更是大部分api都是异步的,小量同步api。这与其余大部分语言恰好相反,也给习惯于同步编程思惟的同窗形成了很大的困扰,就是常说的转不过弯来,习惯性的认为代码是按顺序一行一行的往下执行。
上面介绍了异步非阻塞的概念,那么具体到node.js是怎么实现的呢?这里又会涉及到I/O的概念,具体不讲I/O的细节(操做系统原理都会讲),关键就一点,I/O操做一般比较耗时但不会独占CPU,典型的I/O好比文件读写,远程数据库读写,网络请求等。 先讲耗时,若是用同步API来进行I/O操做,在返回结果以前就只能等待,因此最好的办法上面已经讲过,就是进行异步操做。接着说一下不会霸占CPU的好处。在node.js进程里面,有一个用户线程(javascript所宣称的单线程)和一个异步线程池(用户没法直接访问), 若是跑在异步线程上的代码是阻塞的,那么这种异步根本就起不到消除阻塞的做用,为何?缘由就是阻塞代码会霸占cpu,致使本进程全部代码都等待不论是哪一个线程。可是,,,刚刚讲的node.js里面的I/O API都是不会霸占CPU的,因此是非阻塞的,就不会出现这个问题。这就是node.js的最引觉得傲的特性之一:异步非阻塞I/O.
上面只是强调异步非阻塞,那么对于真正的阻塞代码,node.js怎么办呢?很差意思。。。单个node.js进程真无能为力,跟同步编程语言相比没什么优点,只能用传统的方式,多进程,多开几个node.js进程,甚至多开几个服务器。任何语言都有它的强项和弱项,node.js的强项就是它原本的设计初衷:让开发者可以轻松的编写高性能的web服务器(进行的最多的就是网络和数据库的I/O操做),而不是作大量的CPU密集型的运算(不过我赶脚,就算要作不少阻塞操做,用多进程和多服务器又有何不可?node.js对此提供了足够的支持)。这样算是回答了上一小节最后的问题了吧:)
要理解javascript异步编程和其余语言同步编程的区别,能够从一个最简单的例子开始。在同步为主的语言中,若是须要等待10秒钟,一般是相似sleep 10之类的语句,在10秒以内整个进程挂起,也就是阻塞10秒。但javascript不是这样,它是使用setTimeout函数,从字面意思就已经能够看出区别了, 设一个timeout的时间, 在这段时间内cpu能够继续运行其余代码, 等10秒时间到了, 就用回调函数的形式来作应该10秒以后才作的事情。
附注:也许是我读得书少:) 这么久以来没有发现网上有对异步编程讲解的很透彻的文章,在本身学习过的资料当中,朴灵的《深刻浅出node.js》是讲解的最深刻透彻的,强烈推荐。