码迷,mamicode.com
首页 > 其他好文 > 详细

第三章 从阻塞顺序性编程风格到事件驱动型和异步编程风格

时间:2015-09-17 11:58:37      阅读:359      评论:0      收藏:0      [点我收藏+]

标签:

一 异步与阻塞,事件驱动与顺序执行


1.什么是异步,什么是事件驱动,异步有什么好处,有什么坏处

A君和B君今天都是计划去银行办点事,然后去超市买点日用品.他们都来到银行,A去自动提款机那里开始排队,前面大概有20来人,她只能依次排队等,取到钱后她再去超市.B去到排队机抽了个号码,他一看前面还有很多人,预计要比较长事件,然后去隔壁超市找要买的东西,听到银行广播自己号码时候,再回来银行办事.

对于A君,我们叫同步,程序只能按预定的顺序执行,遇到耗时长的操作时候,需要等待其完成才能执行下一步任务.对于A君,他有个好处就是按部就班,不怕出现意外情况,例如身上钱不够这些问题.但明显会消耗更多时间.

对于B君,我们叫异步,程序遇到需要等待长的操作时候,不等待其完成而马上执行下一步任务,再等到长时间操作完成后再回头去继续处理后续问题.这样好处是节约时间,但如果你事情多的话,这样就变得有些乱,不好管理.

而银行的广播会触发B君回去银行,这个我们叫事件驱动.当然这样不好的地方很多,例如出错处理会更加麻烦,事情会变得混乱不好整理.

2 如何阅读node的代码

由于事件驱动的存在,我们不能像其他的一些代码那样顺序查看,我们应该先大概预览一次代码,然后以"事件"为关键词,搜索代码,找到代码的开头入口.

例如一个简单web程序:

var http = require(‘http‘);

var url = require(‘url‘);

var Web = function(req,res){

    this.req = req;
    this.res = res;

};

Web.prototype[‘/hello‘] = function(){
    this.res.end("hello world");
}

Web.prototype[‘/404‘] = function(){
    this.res.
    this.res.end("404 not found");
}

http.createServer(function(req, res){

 var reqinfo = url.parse(req.url);
 var web = new Web(req, res);
 var path = reqinfo.pathname.toLowerCase();
 if (path && web[path]){
    try{
        web[path]();
    }catch(err){

    throw err;
   }

 }else{
     web[‘/404‘];

 }

}).listen(80);


你可以发现,如果你从头开始看,它是不会符合事情的顺序来的,我们应该从http.createServer这里开始入手.而段代码却是在程序的最后面.
所以看node的代码,你要做的第一件事不仔细看每个代码,而是快速浏览一次,并且找到正确的入口.

(PS:最开始,我作为PHP程序员进入我的前公司,但措手不及的0基础接手了一个离职的研究所写node半成品(半成品都没,因为程序运行不了)项目,我用了PHP的习惯来查看代码,寻找代码的思路,导致了我一周完全摸不着头脑.这种痛苦至今刻骨铭心,也感谢这种刻骨铭心,让我学其他各种语言更加得心应手)

二 回调与避免深度嵌套


1.深度嵌套的典型例子

如下面的这个读取文件内容的函数:

fs.readFile(‘/etc/passwd‘, function (err, data) {
  if (err) throw err;
  console.log(data);
});

那,我们读取两个文件,将这两个文件的内容合并到一起处理怎么办呢?大多数接触js不久的人可能会这么干:

fs.readFile(‘/etc/passwd‘, function (err, data) {
  if (err) throw err;
  fs.readFile(‘/etc/passwd2‘, function (err, data2) {
    if (err) throw err;
    // 在这里处理data和data2的数据
  });
});

那要是处理多个类似的场景,岂不是回调函数一层层的嵌套?

我们常常把这个问题叫做”回调黑洞”或”回调金字塔”:

doAsync1(function () {
  doAsync2(function () {
    doAsync3(function () {
      doAsync4(function () {
    // .... doAsyncN(..)
    })
  })
})

这种层层嵌套的代码给开发带来了很多问题,主要体现在:

1.代码可读性变差(非常严重)
2.调试困难
3.出现异常后难以排查
4.改写困难.

回调黑洞是一种主观的叫法,就像嵌套太多的代码,有时候也没什么问题。为了控制调用顺序,异步代码变得非常复杂,这就是黑洞。有个问题非常合适衡量黑洞到底有多深:如果doAsync2发生在doAsync1之前,你要忍受多少重构的痛苦?目标不单单是减少嵌套层数,而是要编写模块化(可测试)的代码,便于理解和修改。



2.node v0.1.1版本后的解决方法


2.1 Generator

     Generator是为JavaScript设计的一种轻量级的协程。它通过yield关键字,可以控制一个函数暂停或者继续执行,  本身generator的引入并不是用来解决异步问题的,但yield和next接口恰巧可以为我们解决深层回调的问题,让我们接受异步产生的结果,继续完成接下来的任务,与回调的目的一致。
     
generator实际上是一种迭代器。它很像一个可以返回数组的函数,有参数,可以调用,并且会生成一系列的值。然而generator不是把数组中的值都准备好然后一次性返回,而是一次yield一个,所以它所需的资源更少,并且调用者可以马上开始处理开头的几个值。简言之,generator看起来像函数,但行为表现像迭代器。这感觉代码中使用function*来表示generator很有相似.

2.2generator使用, yied,next,function*和co库.

(如果你熟悉go语言或者python 你肯定对这个东西不陌生.)
Node对generator的支持是从
v0.11.2开始的,但因为还没正式发布,所以需要指明--harmony或--harmony-generator参数启用它(NodeJS使用V8引擎,而V8引擎对ES6中的东西有部分支持,所以在NodeJS中可以使用一些ES6中的东西。但是由于很多东西只是草案而已,也许正式版会删除,所以还没有直接引入。而是把他们放在了和谐(harmony)模式下,如果你需要用,需要指明)。
例如运行文件test.js 我们需要

#node --harmony test.js

#forever start -c "node --harmony" test.js //如果使用forever启动,这也是forever支持ES6的方法


首先定义一个简单的generator,并使用它:

function* Foo(x) {   yield x + 1;  var y = yield null; 
  console.log(y);  return x + y;
}

var foo = Foo(5);
foo.next(); // { value: 6, done: false }
foo.next(); // { value: null, done: false }
foo.next(8); // { value: 13, done: true }

generator的定义跟普通函数差不多,只是在 function 关键字后面多了一个 *号。而调用generator后会返回一个generator对象,其中保存了generator的内部执行状态。每调用一次generator的next方法,就会得到一个包含执行结果的对象,含有两个域 value 和 done 。 value 是此次执行generator的返回值, done 为generator是否已经执行完的标志。如果对 done 为 true 的generator对象调用next方法则会抛出Error: Generator has already finished 错误。

generator中使用了一个新的关键字 yield ,它的作用与 return 差不多,除了可以从generator中返回值给外部使用外,还可以暂停该generator的执行,也可以通过next传递值给generator的上一次中断位置(因此y得到8)并从上次中断位置继续执行代码。

我们关注next的代码

var foo = Foo(5); //创建对象,代码不执行
foo.next(); // { value: 6, done: false } next传入了undefined,执行了 x+1 返回 x+1的结果
foo.next(); // { value: null, done: false } next 传入了undefined,执行了 null,返回 null的结果
foo.next(8); // { value: 13, done: true }传入了8 并被y接收 执行了console.log(y)和return x+ y;



待续

三 循环与递归循环


四 错误处理


五 约定的编程风格


第三章 从阻塞顺序性编程风格到事件驱动型和异步编程风格

标签:

原文地址:http://my.oschina.net/gclinux/blog/507458

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!