首先这篇文章是参考阮一峰老师的ES6章节中的generator部分写的,再加上我自己的理解,新手如果要学习ES6推荐去看阮老师的 ES6
异步
所谓的异步就是假如说现在有俩个过程,第一个过程为我正在做饭,第二个过程为在我做饭的期间有一个人来敲门,这种情形对于异步来说就是,我首先需要停止做饭,然后去开门,当人进来之后,我把门关上了,然后继续做饭去了,简单的说就是我停止一个过程,去做另一个过程,然后另一个过程结束后再来继续做原来没有做完的过程
异步处理的几种方式
回调函数的异步
我们以读取文件为例
fs.readFile(fileA, 'utf-8', function(err, data){
fs.readFile(fiileB, 'utf-8', function(err,data)}{)
})
readFile()函数是nodejs中的,首先读取fileA,然后读取fileA结束后调用回调函数然后在读取fileB,上面如果读取多个文件会看起来特别冗余,那么ES6中Promise就起到了作用
Promise函数的异步处理
还是以上述读取文件的例子
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data) {
console.log(data);
}).then(function(){
return readFile(fileB);
}).then(function(data){
console.log(data);
}).catch(function(err){
console.log(err);
})
这个的意思就是引用fs-readfile-promise模板,使用bash的可以用npm install --save-dev fs-readfile-promise来安装,这个模块返回一个promise版本的readFile()所以它可以调用then方法,然后在打印出fileA中的数据,then返回一个promise然后在调用then方法,然后返回promise版本的readFile,返回具有readFile的promise,然后在调用then输出fileB的数据
generator实现异步
var fetch = require('node-fech');
function *gen(){
var url = 'haha';
var result = yield fetch(url);
console.log(url);
}
var g = new gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
首先先创建了一个generator函数,然后fetch函数返回的是一个promise对象,所以首先第一次调用next(),返会的是一个对象,这个对象的value属性就是fetch返回的promise对象,然后调用then函数将数据转为json,然后then函数返回一个promise对象,然后在调用then对象,然后把上一个then返回的数据传递给第二个then里面的function函数
thunk函数
什么是thunk函数
简单的说就是如果你想往一个函数中传递一个x+5,参数,现在就把这个x+5用一个函数表示,如下
var thunk = function(){
return x+5
}
这个用来表示x+5的函数就是thunk
js中的thunk函数
fs.readFile(fileName, callback);
var thunk = function(fileName) {
return function(callback){
return fs.readFile(filename, callback);
}
}
var read = thunk(fileName)(callback);
简单的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);
}
}
}
分析上面Array.prototype.slice.call(arguments);这一行代码,就是slice函数是在原型对象中,然后使slice被arguments调用,因为slice中没有其他参数,所以这行代码直接返回一个数组,这个数组其实就是arguments中各参数组成的
thunkify模块
安装该模块使用 npm install thunkify
var thunkify = require('thunkify');
var fs = require('fs');
read('package.json')(function(err, str){})
下面我们来分析一下thunkify的源码
function thunkify(fn){
return function(){
//第一个返回函数就是把fn的参数传递给args这个数组
var args = new Array(arguments.length);
//ctx就是上下文环境
var ctx = this;
for(var i = 0; i < args.length; ++i){
args[i] = arguments[i];
}
//第二个返回的函数,这里的done相当于回调函数这里保证回调函数只
//被执行一次
return function(done){
var called;
args.push(function(){
if(called) return;
called = true;
done.apply(null, arguments);
})
//执行fn函数然后,这里边的args包括fn函数以及保证回调函数执行一次
try{
fn.apply(ctx, args);
}catch(err){
done(err);
}
}
}
}
function f(a, c, callback){
var sum = a + b;
callback(sum);
callback(sum);
}
var ft = thunkify(f); //第一次调用thunkify函数就是返回第一个函数
var print = console.log.bind(console);
ft(1, 2)(print); //ft(1,2),这时候把1,2加入args,然后这里面的print就相于
//done,也就是callback,所以callback只会被调用一次
自动执行generator函数
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
//下面是怎样实现generator函数自动执行
var g = gen();
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);
});
});
从以上可以看出主要是通过next函数实现generator函数执行的,往next中传入的值,也就是yield返回的值,也就是gen函数中的r1和r2
thunk实现generator函数自动执行
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};
run(g);
这里面的在yield后面的函数就是thunk函数,上面的next函数就是thunk函数的回调函数,也就是执行的函数,所以上面的generator能够一直自动执行
CO模板实现generator函数自动执行
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模块和上面的run函数的功能一样,所以这个co模块和run函数的实现都差不多
promise来实现generator自动执行
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return 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());
};
上面吧readFile函数包装成一个promise对象,以实现在动执行
CO源码分析
function co(gen) {
var ctx = this;
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(
new TypeError(
'You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "'
+ String(ret.value)
+ '"'
)
);
}
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
});
}
上面代码中,next函数的内部代码,一共只有四行命令。
第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。
第二行,确保每一步的返回值,是 Promise 对象。
第三行,使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数。
第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为rejected,从而终止执行。
有时间继续分析这个源码今天太累了