您的位置:首页 > 编程语言

异步编程优势难点及解决方案

2015-11-20 17:50 387 查看
异步:简单说为一个任务分成两段,先执行第一段,然后执行其他任务,等做好了准备,再转过头执行第二段

异步和非阻塞是两个不同的概念

异步I/O和非阻塞I/O的区别:

阻塞造成CPU等待I/O,浪费等待时间,非阻塞I/O跟阻塞I/O的差别为调用之后会立即返回。

非阻塞的问题是由于完整的I/O并没有完成,立即返回的并不是业务期望的数据,而仅仅是当前调用状态。为了获得完整数据需要重复调用I/O操作确认是否完成,异步I/O可实现不等待数据读取完成。

优势:

特性是基于事件驱动的非阻塞I/O模型,非阻塞I/O可以使CPU与I/O并不互相依赖等待

难点:

1.异常处理

  处理异常约定,将异常作为回掉函数的第一个参数传回,如果为空则说明没有异常

2.函数嵌套过深

3.阻塞代码

 没有sleep的函数,只能使用setTimeout()

4.多线程编程

浏览器多线程Web Workers,nodejs的工作线程child_process是其基础API

5.异步转同步

异常编程解决方案

0.回调函数Callback

Javascript语言对异步编程的实现就是回调函数。回掉函数就是把任务的第二段单独写在一个函数里面,等重新执行这个任务的时候直接调用这个函数。

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

readFile函数就是回调函数,回掉函数的第一个参数必须是错误对象err,原因是程序分成两段,在两段之间抛出的异常程序无法捕捉,只能当作参数传入第二段。

如果多层回调就会造成恶魔金字塔问题

1.Promise/Deferred模式

Promise是为了解决多重嵌套回调问题提出来的。不是新的语法功能,而是一种新的写法

Deferred主要是用于内部用于维护异步模型的状态;Promise则作用于外部,通过then()方法暴露给外部添加自定义逻辑

Promise操作只有三种状态:未完成态 --完成态  |--- 失败态

var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function(data){
console.log(data.toString());
})
.then(function(){
return readFile(fileB);
})
.then(function(data){
console.log(data.toString());
})
.catch(function(err) {
console.log(err);
});

每个.then()方法都会返回一个新的promise

调用fs-readfile-promise模块,它的作用是返回一个Promise版本的readFile函数。提供then方法加载回掉函数,catch方法捕捉执行过程中抛出的错误。

promise的最大问题是代码冗余,原来任务被promise包装了一下,不管什么操作都是一堆then,原来的语义变得不清楚

2.事件发布/订阅模式

  是回调函数的事件化。Node自身提供的events模块是发布/订阅模式的简单实现。

  可利用事件队列解决雪崩问题例如:

var proxy=new events.EventEmitter();
var status="ready";
var select=function(callback){
proxy.once("selected",callback);
if(status==="ready"){
status="pending";
db.select("SQL",function(results){
proxy.emit("selected",results);
status="ready";
});
}
}


3.generator函数

协程(coroutine):多个线程互相协作,完成异步任务。大致流程如下

-第一步,协程A开始执行

-第二步,协程A执行到一半,进入暂停,执行权转移到协程B

-第三步,(一段时间后)协程B交还执行权

-第四步,协程A恢复执行

协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。

整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。

异步任务的封装

var fetch = require('node-fetch');

function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}

Generator函数封装了一个异步操作。这段代码非常像同步操作,除了加上了yield命令

var g = gen();
var result = g.next();

result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});

首先执行Generator函数,获取遍历器对象,然后执行next方法,执行一部任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法

Thunk函数

JavaScript语言是传值调用,它的Thunk函数替换的是多参数函数,将其替换成单参数的版本,且只接受回掉函数作为参数。

通过高阶函数实现

//正常版本的readfile(多参数版本)
fs.readFile(fileName,callback);

//Thunk版本的readFile(单参数版本)
var readFileThunk=Thunk(fileName);
readFileThunk(callback);

var Thunk=function(fileName){
return function(callback){
return fs.readFile(fileName,callback);
}
}

简单的Thunk函数转化器

var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};

转换器的使用

var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

但是在生产环境应使用Thunkify模块

Generator函数的流程管理

var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

var gen = function* (){
var r1 =  yield readFile('/etc/fstab');
console.log(r1.toString());
var r2 = yield  readFile('/etc/shells');
console.log(r2.toString());
}

yield命令用于将程序的执行权移出Generator函数,那么就需要一种方法将执行权交还给Generator函数

这种方法就是Thunk函数,因为它可以在回调函数里,将执行权交给Generator函数。自己先手动执行上面这个Generator函数

var g = gen();

var r1=g.next();
//值是一个函数,需要传入回调函数
r1.value(function(err,data){
if(err) throw err;
var r2=g.next(data);
r2.value(function(err,data){
if(err) throw err;
g.next(data);
});
});

原理是将同一个回调函数,反复传入next方法的value属性。使我们可以用递归来自动完成这个过程。

以下函数是一个简单的Generator执行器

//执行器函数
function run(fn) {
//获得遍历器
var gen = fn();
//执行方法 递归
function next(err, data) {
//获得yield结果 value是一个函数 data参数给Generator函数上一个yield的返回值
var result = gen.next(data);
if (result.done) return;
//递归调用 执行方法
result.value(next);
}
//执行递归
next();
}
//开始执行Generator函数
run(gen);

不管有多少个异步操作,直接传入run函数即可。当然,前提是每一个异步操作,都要是Thunk函数,也就是说,跟在yield命令后面的必须是Thunk函数

co模块的原理

Generator函数自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点:

-1 回调函数。将异步操作包装近Thunk函数,在回掉函数里面交回执行权

-2 Promise对象。将异步操作包装成Promise对象,用then方法交回执行权

回调函数的方法上文已说,下文介绍封装成promise对象的方法

var fs = require('fs');
//调用方法封装为promise函数
var readFile = function(fileName){
return new Promise(function(resolve,reject){
fs.readFile(fileName,function(error,data){
if(error) reject(error);
resolve(data);
});
});
};

var gen = function* (){
var r1 =  yield readFile('/etc/fstab');
var r2 = yield  readFile('/etc/shells');
console.log(r1.toString());
console.log(r2.toString());
}

手动执行上面的Generator函数

var g = gen();

g.next().value.then(function(data){
g.next().value.then(function(data){
g.next(data);
});
});

手动执行用的是then方法,层层回掉函数。可写一个自动执行器

//执行器函数
function run(gen){
//获得遍历器
var g = gen();
//执行方法 递归
function next(data){
//获得yield结果 value是一个函数 data参数给Generator函数上一个yield的返回值
var result=g.next(data);
if(result.done) return result.value();
//递归调用 执行方法
result.value.then(function(data){
next(data);
});
}
//执行递归
next();
}
//开始执行Generator函数
run(gen);


4.流程控制库

  1.尾触发与Next

  2.async

  3.Step

  4.wind

参考:http://es6.ruanyifeng.com/#docs/async      《深入浅出nodejs》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: