最近nodejs看得有点多,感觉满脑子都是文件处理,异步编程,buffer等,发现学node学多了,前端用到的javascript也会更加豁然开朗,特别是廖雪峰和阮一峰老师的博客,给了我不小的帮助。我想从nodejs开始说起,毕竟这才是编程的核心,现在发现前端用的js只是熟练运用onclick事件等,自己以前还是只接触到了皮毛啊。
回到主题,据传当初Ryan Dahl选择js来作为后台语言时,主要就是看重了js的单线程,同步运行,适合一些高并发的场景,这就从侧面说明了js这门语言的特性:同步,单线程。那么可以多线程异步编程吗?答案当然是肯定,那么,js异步编程有哪几种方法,这几种方法各自的优缺点又如何呢?
说到这里,我又想解释一下同步与异步,第一次听到这两个词语,就要得追溯到大学的操作系统课堂了,OS的运行机制(运行模式)分为同步(Synchronous)和异步(Asynchronous)。
“同步模式”就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;”异步模式”则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。”异步模式”非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,”异步模式”甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
所以针对js的异步编程方法,我准备讲解如下几个方法:
一,回调函数
这是异步编程最最最基本的方法,几乎所有能异步编程的语言都可以用到这个方法。
回调函数的具体意思是,假设有两个函数,a和b,函数a在前b在后,正常情况的话,程序运行到了函数a,会等待其执行完毕,然后拿到函数a的运行结果,才会继续执行函数b;那么这时,问题就来了,如果函数a运行一秒,我可以等,运行十秒,就难以忍受了,万一函数a卡住了,永远成功不了,你会等到海枯石烂吗?哈哈哈,那怎么办,我下面还有千千万万行的代码啊,辛辛苦苦写了那么久,还没运行到这里,就结束了,逗我啊。。。这个时候,就该回调函数上场了,把函数b写成函数a的回调函数,像这样:
|
|
采用这种方式,我们把同步操作变成了异步操作,a不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。
那么问题就来了,学。。。不对,这会产生什么问题?如果回调函数比较多会怎样?
|
|
是不是觉得很傻,代码毫无可读性而言,不过,这是最基本的js异步编程的方法。
二、promise
有了最基本的方法,就会有很多人不满足,想做出优化,写个第三方库啊或者发各种包,各种模块什么的。
最有名的,莫过于promise了,做为前端,大名鼎鼎的jQuery肯定不会不知道吧,说起jQuery,要说的就太多了,赶紧收住。jQuery里的ajax方法,就是典型的参照了promise设计出来的产物,其使用方法是:
|
|
这是一套典型的jQuery版的ajax应用方法,后面的done,就是指的前面做完了,返回一个结果retData来,然后再执行其它操作。
在es6中,已经正式出台了promise对象,其基本用法如下:
|
|
是不是一下子就简洁了很多?当然,这个也不能说是完美的异步编程解决方案,只能说是完善罢了,其优点确实是简化了不少代码,不过也有不足的地方,那就是维护比较麻烦,因为这样的话,异步多了,你一眼看过去,将会看到一堆的then。。。是不是很尴尬啊,代码的可读性一下子就降低了,你满脑子的then。
三、观察者模式
说起这个,又要回到大学的操作系统课堂了,算了,不浪费时间了,直接进入正题。
观察者模式,你可以理解为,存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”(publish)一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。
这个方式的实现有很多的插件,就拿我们最熟的jQuery来说好了。
|
|
这个也有一点可以优化的地方,就是发布和订阅的消息太多时,不好管理,事件会太杂乱,有一个整体管理事件的中心就更好了。
四、generator
Generator函数是协程在ES6的实现,最大特点就是可以交出函数的执行权(即暂停执行)。其核心意思就是,一段代码执行到某个节点时,暂停执行,然后执行另一段代码,有等了会,再回过头来执行第一段代码剩下的部分,总的来说就是几个代码片段交替执行。
说到这里我觉得现现在generator函数并没有被广泛的大面积的运用,所以先花点时间来介绍下generator函数吧。talk is cheap,show u the code.
|
|
上面的代码中,调用generator函数,会返回一个内部指针(也叫遍历器),就是这里的g,所以说当你直接调用generator函数的时候,它并不会直接返回结果,而里面的yield字段,就表示异步操作需要暂停的地方,通过g指针调用next方法,指针就会开始异动到下一个yield(或者return)的地方去,并且返回当时暂停时所得到的结果value,以及遍历状态done。
下面来看看如何使用generator函数来执行一个真实的异步任务。
|
|
这里我们通过发送请求,然后输出请求返回的结果,去掉yield命令,就完完全全是同步的了,所以这也是yield的一个比较大的特点,对代码改动小,保持代码可读性。。。话说回来,现在声明了这个函数,如何使用呢?使用方法如下:
|
|
上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next 方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next 方法。
可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
发现已经写了好多了,看来一次性写完确实太累啊,留下一个acync函数下次再写吧。