对于编程时的I/O操作,从最开始用的setTimeout()到jQuery的Deferred,以及近两年经常用到的Promise、Generator、async,深深感觉到技术一直在进步,能解决的问题越来越多,使用越来越方便,今天正好有时间就把这些东西大致整理下。可以去下载源码测试,直接node app1.js就可以看到结果,对于最后一个例子需要node v7.2.0以上。
本文的源码放在了 https://github.com/binginsist/binginsistNote/tree/master/javaScript回调进化史
I/O操作的问题
我们都知道当遇到I/O操作时,会出现代码不按顺序执行的问题,比如下面这个例子,虽然“console.log("虽然我在最后,但是我第一个输出,不信你瞧")”这句话在最后的位置,可是这句话是先输出的,并且另外三个操作也不是按顺序输出的。这给我们的编程带来很大的麻烦,我们无法使用前面操作的结果。一个方法就是使用setTimeout(),setTimeout()可参考这个链接http://www.css88.com/archives/5804,当然另一个方法就是使用回调,也是本文主要讲的内容。
//1.I/O操作案例(用读取文件的方式模拟)
var fs = require("fs");
fs.readFile("./data/1.txt",function(err,data){
console.log(data.toString());
});
fs.readFile("./data/2.txt",function(err,data){
console.log(data.toString());
});
fs.readFile("./data/3.txt",function(err,data){
console.log(data.toString());
});
console.log("虽然我在最后,但是我第一个输出,不信你瞧")
//输出结果: 虽然我在最后,但是我第一个输出,不信你瞧,2.txt,1.txt,3.txt(不按顺序输出所以结果不一定)
回调地狱案例
这个例子中,我们有三个读取文件的操作需要按顺序输出,可以使用这种方式。现在才3层嵌套,我都看不下去了,如果有要读取10个文件怎么办,这就进入了所谓的回调地狱。
//2.回调地狱案例
var fs = require("fs");
fs.readFile("./data/1.txt",function(err,data){
console.log(data.toString());
//第二层
fs.readFile("./data/2.txt",function(err,data){
console.log(data.toString());
//第三层
fs.readFile("./data/3.txt",function(err,data){
console.log(data.toString());
});
});
});
//输出结果: 1.txt,2.txt,3.txt(按顺序输出)
Promise进化
2015 年 6 月ES6正式发布,其提供的 Promise 对象着实让我们方便了一把,从此我们走上了将异步操作以同步操作的流程表达出来的光明大道。喜欢jQuery的Deferred得朋友可能会感到非常熟悉,是的他们很像!
//3.Promise案例
var fs = require("fs");
function readText(text) {
//用Promise包装读取文件的方法,return回Promise对象
return new Promise(function(resolve, reject) {
fs.readFile("./data/"+text,function(err,data){
dataTwo=data.toString();
//将本次函数执行后的结果dataTwo通过resolve抛出
resolve(dataTwo);
});
});
}
//调用readText函数
readText("1.txt").then(function(data){
//data接收resolve抛出的结果
console.log("第1次读取文件的结果"+data);
//执行一些操作后,调用第二个函数并return回
return readText("2.txt");
}).then(function(data){
//data接收到第二个函数resolve抛出的结果
console.log("第2次读取文件的结果"+data);
//执行一些操作后,调用第三个函数并return回
return readText("3.txt");
}).then(function(data){
//data接收到第三个函数resolve抛出的结果
console.log("第3次读取文件的结果"+data);
//再执行其他方法
})
//输出结果:
// 第1次读取文件的结果1.txt
// 第2次读取文件的结果2.txt
// 第3次读取文件的结果3.txt
Promise.all
当然只有Promise是不够的,只是变成同步并不能让我们满意,比如我们一个页面有很多请求,等数据都加载好了再去取消loading页面;或者需要把所有的文件读取完再进行某种操作。能不能让那些操作并发进行,最后再一次性执行其他操作,事实告诉我们这是可以的,因为有了Promise.all,请看下面案例。
//4.Promise.all案例
var fs = require("fs");
function readText(text) {
//用Promise包装读取文件的方法,并返回Promise对象
return new Promise(function(resolve, reject) {
fs.readFile("./data/"+text,function(err,data){
dataTwo=data.toString();
//将本次函数执行后的结果dataTwo通过resolve抛出
resolve(dataTwo);
});
});
}
let arr = [];
//并发处理读取文件的操作
arr.push(readText("1.txt"));
arr.push(readText("2.txt"));
arr.push(readText("3.txt"));
Promise.all(arr).then(function (datas) {
// 最终处理,datas 是所有返回结果的数组
console.log("返回数据为", datas);
});
//输出结果:[ '1.txt', '2.txt', '3.txt' ]
Generator
Generator 的基础这里就不细说了,可以参考http://es6.ruanyifeng.com/#docs/generator这篇文章,这里要注意的是Generator 函数需要有启动器运行它,就是下例中的run函数。
//5.Generator案例
var fs = require("fs");
function readText(text) {
return new Promise(function(resolve, reject) {
fs.readFile("./data/"+text,function(err,data){
dataTwo=data.toString();
resolve(dataTwo);
});
});
}
//5.Generator函数
function* gen() {
let text1 = yield readText("1.txt");
let text2 = yield readText("2.txt");
let text3 = yield readText("3.txt");
console.log(text1, text2, text3);
}
//Generator函数启动器
function run(gen) {
var g = gen();
function next(d) {
var r = g.next(d);
r.done || r.value.then(function(d){ next(d) }); // 这个是关键,把值传回传
}
next();
}
//运行启动器
run(gen);
//输出结果: 1.txt 2.txt 3.txt
async
作为Generator的语法糖,async更加简化了我们的代码,让我也爱上了这种方式,并且一个词生动形象的表现出这个函数的作用,没错它就是await。但是要注意的是node 的低版本不支持这些特性,建议node v7.2.0以上。
//6.async案例(注意使用node v7.2.0以上)
var fs = require("fs");
function readText(text) {
return new Promise(function(resolve, reject) {
fs.readFile("./data/"+text,function(err,data){
dataTwo=data.toString();
resolve(dataTwo);
});
});
}
async function run() {
let text1 = await readText("1.txt");
let text2 = await readText("2.txt");
let text3 = await readText("3.txt");
console.log(text1, text2, text3);
}
run();
//输出结果: 1.txt 2.txt 3.txt