ES6的异步操作
刚开始看书上的这一章的时候,没想到JavaScript还有异步操作,还有这种操作???果不其然,异步操作和java里面的异步操作同样,还是有点难。不过看了两三遍下来,似乎还是明白了一些。
废话不多说,那就直接进入正题吧!
重要性:JavaScript只有一个 线程,如果没有异步编程,得卡死,基本没法用。
异步
1.概念:简单来说就是不连续的执行,比如说,有一个任务分成两段,先执行第一段,转而执行其他任务,等做好准备再回过头执行第二段。
2.JavaScript语言对异步编程得实现就是回调函数,所谓回调函数,就是把任务的下一阶段单独写在一个函数中,等到重新执行该任务的时候直接调用这个函数
//读取文件进行处理是这样写的。
fs.readFile('/etc/passwd',function(err,data){
if(err) throw err;
console.log(data);
})
//node.js约定回调函数的第一个参数必须是错误对象err(如果没有错误,该参数就是null)
//原因是执行分成两段,在这两段之间抛出的错误程序无法捕捉,只能当做参数传入第二段
这里我有一个大胆的想法:是不是只要一个函数的第一个参数是err,都可以认为是回调函数呢?这个猜想暂时放在这里。哈哈
3.Promise的引入
其实回调函数本身没有什么问题,问题在于多个回调函数嵌套的话,比如读取第一个文件之后,再读取第二个文件,再读取第三个文件...读取第n个文件,这样下去会写很多个回调函数,gg。
因此,为了解决这个问题,引入了Promise,它不是一种新的语法功能,而是一种新的写法。例如:
var readFile = require('fs-readfile-promise');
readFile(fileA).then(function(data){
console.log(data.toString());
}).then(function(data){
return readFile(fileB);
}).then(function(data){
console.log(data.toString());
}).catch(function(err){
console.log(err);
})
上面代码存在的主要问题是代码冗余,原来的任务被Promise包装了一下,不管什么操作,一眼就能看出去都是一堆then,原来的语义变得很不清楚。
4.Generator函数的数据交换和错误处理
(1)这个函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因
(2)next方法返回值的value属性,是Generator函数向外输出数据,next方法可以接受参数,向Generator函数体内输入数据。
(3)Generator函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
5.用Generator函数执行一个真实的异步任务
var fetch = require('node-fetch');
function* gen(){
var url = 'http://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}//上述代码中,Generator函数封装了一个异步操作,先读取一个远程的接口
//然后从JSON格式的数据解析信息,就像前面说的,这段代码非常像同步操作,只是加上了
//yield命令
var g = gen();
var result = g.next(); //fetch模块返回的是一个Promise对象
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
6.Thunk函数
(1)编译器的传名调用实现:将一个参数放在一个临时的函数中,然后将这个临时的函数传入函数体,这个临时的函数就是Thunk函数。
(2)JavaScript语言的Thunk函数:JavaScript语言是传值调用,它的Thunk函数含义有所不同。在JavaScript语言中,Thunk函数替换的不是表达式,而是多参数函数,它将其替换成单参数的版本,且只接受回调函数作为参数。
//简单的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);
}
}
}
//简单的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);
}
}
}
(3)Generator函数的流程管理
以读取文件为例子
用到的函数及作用:
1.Thunk函数,这个可以通过Thunkify模块,返回一个Thunk函数(并以回调函数作为它的参数)
2.Generator函数,这个函数主要用来封装两个或者两个以上的异步操作(通过yield命令后接异步操作)
,然后通过调用这个Generator函数获取到遍历器对象,然后这个对象调用next()方法来执行yield
命令之后的异步操作,执行成功之后会返回一个对象,这个对象的value的值也就是一个只接收回调函数
的一个Thunk函数,而这个回调函数主要用next方法将执行权交回给Generator函数,像这样下去,
可以继续执行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());
}
//如何手动的执行上面这个Generator函数
var g = gen();
var result = g.next();
result.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);
})
})
//可以方法上述Generator函数的执行过程是将同一个回调函数反复传入next方法的value属性。
//其实我们可以用递归来实现这个过程。
(4)Thunk函数的自动流程管理
Thunk函数真正的魅力在于可以自动执行Generator函数。
//下面通过一个run方法来实现Generator函数的自动执行,利用的主要是Thunk函数
function run(fn){
var gen = fn();
function next(err,data){
var result = gen.next(data);
if(result.done) return;
result.value(next);
}
next();
}
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());
}
需要明白的几个点:
1.之所以能够自动执行Generator函数,是因为有Thunk函数的存在
2.Generator函数中的异步操作必须要是Thunk函数
3.在这个run方法中写这个next方法,作为Thunk函数参数的回调函数
4.在next方法中,继续将next方法传递给Thunk函数,实现递归,也就是继续自动执行
实现Generator函数自动执行的方案有:1.Thunk函数 2.Promise对象
(5)co模块:这个是一个小工具,用于Generator函数的自动执行
co模块可以让你不用编写Generator函数的执行器,Generator函数只要传入co函数就会自动执行
co函数会返回一个Promise对象,因此可以使用then方法添加回调函数。
co模块的原理
co模块其实就是一个异步操作的容器,它的自动执行只需要一种机制,当异步操作有了结果能够自动交回执行权。有两种方法:
1回调函数。将异步操作包装成Thunk函数,在回调函数中交回执行权
2.Promise对象。将异步操作包装成Promise对象,在then方法中交回执行权。
co模块的实质
将Thunk函数和Promise对象这两种自动执行机制,包装成了一个模块。使用co模块的前提条件是,Generator函数的yield命令只能是Thunk函数或者是Promise对象
var fs = require('fs');
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 f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
})
})//手动执行也就是不断的添加回调函数
//下面自己写一个自动执行器
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if(result.done) return;
result.value.then(function(data){
next(data);
})
}
next();
}
co模块可以处理并发的异步操作
co支持并发的异步操作,即允许某些操作同时进行,等到他们全部完成才进行下一步
这时要把并发的操作都放在数组或者对象里面,跟在yield语句后面
//数组的写法
co(function* (){
var res = yield [
Promise.resolve(1);
Promise.resolve(2);
];
console.log(res);
}).catch(onerror);
//对象的写法
co(function* (){
var res = yield {
1:Promise.resolve(1);
2:Promise.resolve(2);
};
console.log(res);
}).catch(onerror);
co(function* (){
var values = [n1,n2,n3];
yield values.map(somethingAsync);
}) //这里允许并发3个somethingAsync异步操作,等到他们全部完成才会进行下一步
function* somethingAsync(x){
return y;
}