[node] Thunk、co模块
#
Thunk
Thunk函数的定义,它是"传名调用"的一种实现策略,用来替换某个表达式。
#
var thunk = function () {
return x + 5;
};
function f(thunk){
return thunk() * 2;
}
JavaScript是传值调用。
在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 readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);
// ES5版本
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
// ES6版本
var Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
#Generator 结合 Thunk 的流程管理
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());
};
var g = gen();//变量g是Generator函数的内部指针,表示目前执行到哪一步。
var r1 = g.next();//next方法负责将指针移动到下一步,并返回该步的信息(value属性和done属性)。
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);
});
});
Thunk函数的自动流程管理
#Generator的自动执行器
var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
var gen = function* (){
var f1 = yield readFile('fileA');//要保证yield后边是thunk函数
var f2 = yield readFile('fileB');
// ...
var fn = yield readFile('fileN');
};
run(gen);//只要执行run函数,这些操作就会自动完成
co模块
co模块用于Generator函数的自动执行。
可以让你不用编写Generator函数的执行器。
co函数返回一个Promise对象,因此可以用then方法添加回调函数。
co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。
使用co的前提条件是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。
co支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。
#基本用法
var co = require('co'); var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; //co(gen);//自动执行 co(gen).then(function (){ console.log('Generator 函数执行完成'); });
#基于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; result.value.then(function(data){ next(data); }); } next(); } run(gen); ////
#处理并发的异步操作
把并发的操作都放在数组或对象里面,跟在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);
#三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。
co(function* () { var values = [n1, n2, n3]; yield values.map(somethingAsync); }); function* somethingAsync(x) { // do something async return y }
#co.wrap()
//读取文件1 co(function* (){ var filename = yield readFile('hello1.txt', 'utf-8'); return filename; }).then(console.log, console.error); //读取文件2 co(function* (){ var filename = yield readFile('hello2.txt', 'utf-8'); return filename; }).then(console.log, console.error); //使用co.wrap() var getFile = co.wrap(function* (file){ var filename = yield readFile(file, 'utf-8'); return filename; }); getFile('hello.txt').then(console.log); getFile('hello2.txt').then(console.log);