Node.js里的异步调用是通过回调函数实现的,不管javascript的语言特性如何转变,至少在Node.js的native api里,肯定是回调函数。
比如读取文件
fs.readFile('/home/wqf/a.txt', function (err, data) { if (err) throw err; console.log(data); fs.readFile('/home/wqf/b.txt', function (err, data) { if (err) throw err; console.log(data); }); });
回调函数面临一个最大的问题就是callback hell,也就是多个异步操作有先后顺序时,会嵌套很多的回调函数。
为了防止这种情况,后来es6添加了yield关键字,我们看看它是如何运行的
function* asnycProc() { var data1 = yield readFile('/home/wqf/a.txt'); console.log(data1); var data2 = yield readFile('/home/wqf/b.txt'); console.log(data2); }
这个方法要做的事情和上面的回调写法没啥区别,实际上可以把每个yield语句以后的处理,当成yield语句所调用方法的回调。
但是毕竟后面的处理并不是一个方法,所以也不能用普通的方式进行回调,那么后面的处理是怎么执行的呢?
答案是next()
var g = asnycProc(); g.next(); g.next();
但是我们知道native api是通过回调方法执行的,默认不可能执行next()方法,那么就必须包装一下,通过包装一个执行next()的回调方法给native api用,但是next()方法是通过上面的形式执行的,我们没有办法直接把next()包装到回调方法里,必须得借助外力来执行这个next()。
这里有两种方法,一种是通过thunk函数,一种是通过promise对象。
关于thunk函数的介绍可以看这篇
http://www.ruanyifeng.com/blog/2015/05/thunk.html
目的就是把方法的其他参数跟回调方法参数分离,最终生成的方法就只有一个回调方法参数。
写个简单的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); } }; };
那么上面的readFile方法就可以写成
var readFile = Thunk(fs.readFile); readFile('/home/wqf/a.txt');这个方法返回了一个只能接收回调函数的方法
那么我们要做的事情就是下面这个语句返回的是native api里回调函数的参数data
yield readFile('/home/wqf/a.txt');
这又是怎么做到的呢?对于Generator方法,第一个yield语句的返回值是通过第二个next()方法传参回去的。
那么可以通过执行一下方法实现
var g = asnycProc(); 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); }); });
这里r1.value,就是yield readFile('/home/wqf/a.txt');语句中yield后面的值,也就是一个只可以接受回调方法参数的方法。然后再通过第二个g.next(data)方法把结果传了回去。
上面的这个执行函数顺序执行的处理是一样的,所以可以用递归来执行。
function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } run(asnycProc);
接下来再看看promise的实现方法
把readFIle方法包装成promise对象
var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) reject(error); resolve(data); }); }); };那么执行应该是这样的
var g = asnycProc(); 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; result.value.then(function(data){ next(data); }); } next(); } run(asnycProc);
var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };调用很简单只要直接写
asyncReadFile();