异步方案进化过程: callback ---> promise ---> generator + co ---> async + await 本文以nodejs中最常用的文件异步读取操作readFile为例,来说明各个方案下的实现方式。
前提
假设我们现在有两个文件1.txt和2.txt,1.txt的内容是'./2.txt'(2的路径),2.txt的内容是'异步,又见异步'。
需求1
拿到1的内容作为路径去读取2的内容打印出来。
需求2
同时读取两个文件,在两个文件都读取完成时,将文件中的内容合并。
callback
需求1实现
fs.readFileSync('./1.txt', 'utf8', function(err, data) {
if(err) { throw err; }
fs.readFileSync(data, 'utf8', function(err, data) {
if(err) { throw err; }
console.log(data);
})
})
此时我们很容易利用了回调嵌套,嵌套多了就容易累积出回调地狱。
需求2实现
这个需求实现我们先需要了解一个lodash的_after函数
_after函数
_after函数返回一个函数,在这个函数调用次数达到一定限度时,调用回调函数。下面写一个自己的after函数:
function after(times, callback) {
return function() {
if(--times === 0) {
callback();
}
};
}
let sleep = after(3, function() {
console.log('睡饱了');
});
sleep();
sleep();
sleep();//打印出睡饱了
写一个afterRead函数来得到一个read函数,如下:
function afterRead(times, callbacks) {
let arr = [];
return function(path) {
fs.readFile(path, 'utf8', function(err, data) {
if(err) { throw err; }
arr.push(data);
if(--times === 0) {
callbacks(arr);
}
});
}
}
let read = afterRead(2, function(dataArr) {
console.log(dataArr);
});
read('./1.txt');
read('./2.txt');//读完第二个文件打印出['./2.txt', '异步,又见异步']
Promise
需求1实现
function read(path) {
return new Promise(function(resolve, reject) {
fs.readFile(path, 'utf8', function(err, data) {
if(err) { reject(err); }
resolve(data);
});
});
}
read('./1.txt').then(function(data) {
return read(data);//将产生的promise返回出去进行下一步处理
}, function(err) {
console.log(err);
}).then(function(data) {
console.log(data);
});
需求2实现
使用Promise.all(),能够方便进行处理,并且返回的数据是按照请求发起的顺序存放
Promise.all([read('./1.txt'), read('./2.txt')]).then(function(data) {
console.log(data);
});
Generator + co
Generator是一个能够返回迭代器的函数,需要在function关键字和名字之间加上*以标识是个Generator,需要配合yield使用,方法每次执行到yield就会暂停,等待返回的迭代器调用next()方法继续往下执行。
next方法
返回一个包含value和done属性的对象,value表示值,done布尔类型,表示迭代是否完成。
co
co库可以自动的将generator进行迭代,同时每次迭代都会返回一个promise
模仿co库的功能
function co(it) {
let task = it();//taskDef生成一个任务迭代器
return new Promise(function(resolve, reject) {
function step(data) {
let {done, value} = task.next(data);
if(!done) {
value.then(function(data) {
step(data);
}, reject);
} else {
resolve(value);
}
}
step();
});
}
需求1实现
function *readTask() {
let path = yield read('./1.txt');
let content = yield read(path);
return content;
}
co(readTask).then(function(data) {
console.log(data);
});
终极方案 asyn await
async和await(语法糖) === co + generator
用async 来修饰函数,aysnc需要配await,await只能promise
需求1实现
async function readTask() {
try {
let path = await read('./1.txt');
let content = await read(path);
return content;
} catch (error) {
throw err;
}
}
readTask().then(function(data){
console.log(data);
});