Generator函数的异步应用
传统方法
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
基本概念
异步
- 不连续的执行,即某一任务先执行一段,待响应后继续执行另一端。
- 同步是连续执行,不能插入其他任务,因此执行操作的时间程序只能等待。
回调函数
把任务的第二段写在一个函数里,等到重新执行这个任务时直接调用这个函数。
Promise
为解决多重嵌套的写法,允许将回调函数的嵌套改写成链式调用。
Generator函数
协程
异步编程(多任务)的解决方案中其中一种,即多个线程互相协作,完成异步任务。
- 协程A开始执行
- A暂停,B获得执行权
- B交还执行权
- A恢复执行
function *asyncJob() {
// some your code
var f = yield readFile(fileA);
// some your other code
}
协程的Generator函数实现
特点:交出函数的执行权(暂停执行),需要暂停的地方用yield语句
能封装异步操作的原因:
- 可以暂停执行和恢复执行(
根本原因
) - 数据交换
- 错误处理机制
Generator函数的数据交换和错误处理
- 数据交换
function* gen(x) {
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false}
g.next(2) // { value: 2, done: true}
- 错误处理
function* gen(x) {
try {
var y = yield x + 2;
} catch (e) {
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了'); //出错了
异步任务的封装
Generator函数执行异步任务
var fetch = require('node-fetch');
function* gen() {
var url = 'url';
var result = yield fetch(url);
console.log(result.bio);
}
执行操作
var g = gen();
var result = g.next();
result.value.then(function(data) {
return data.json();
}).then(function(data) {
g.next(data);
});
Thunk函数
自动执行Generator函数的一种方法
参数的求值策略
var x = 1;
function f(m) {
return m * 2;
}
f(x + 5)
- 传值调用
在进入函数体之前就计算x+5,传入6
比较简单,但对参数求值时,实际上还没有用到这个参数,可能造成性能损失。 - 传名调用
直接将x+5传入函数体,用到的时候求值
Thunk函数的含义
传名调用
的实现是将参数放到一个临时函数中,将临时函数传入函数体内,临时函数就是Thunk函数
function f(m) {
return m * 2;
}
f(x + 5);
等同于
var thunk = function() {
return x + 5;
}
function f(thunk) {
return thunk() * 2;
}
Js的Thunk函数
// 正常版本的readFile( 多参数版本 )
fs.readFile(fileName, callback);
// Thunk版本的readFile( 单参数版本 )
var Thunk = function(fileName) {
return function(callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
Thunkify模块
生产环境中的转换器建议使用Thunkify模块
- 安装
$ npm install thunkify
- 使用方式
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
read('package.json')(function(err, str){
// your code
});
Generator函数的流程管理
以读取文件为例
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
// 使用Thunk函数将执行权再交还给Generator函数
var gen = function* () {
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield.readFileThunk('/etc/shells');
console.log(r2.toString());
};
Thunk函数的自动化流程管理
基于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();
}
function* g() {
// ...
}
run(g);
co模块
基本用法
用于Generator函数自动执行,co函数返回一个Promise对象,可以用then方法添加回调函数
co(gen).then(function () {
console.log('Generator 函数执行完成');
});
原理
Generator是一个异步操作的容器,它的自动执行需要可以自动交回执行权的机制。
- 回调函数。将异步操作包装成Thunk函数,在回调函数里面交回执行权
- Promise对象。将异步操作包装成Promise对象,用then方法交回执行权
co模块将两种自动执行器(Thunk函数和Promise对象)包装成一个模块,因此yield命令后面只能是Thunk函数或Promise对象。
基于Promise对象的自动执行
以读取文件为例
- 将fs模块的readFile方法包装成一个Promise对象
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(err, data) {
if (err) return reject(err);
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());
};
- 手动执行上面的Generator函数
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);
co模块的源码
function co(gen) {
var ctx = this;
// 接受Generator函数作为参数,返回一个Promise对象
return new Promise(function(resolve, reject) {
// 检查是否为Generator函数,是就执行,得到一个内部指针对象。不是就返回
if (typeof gen === 'function') gen = gen.call(ctx);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
// 将next方法包装成onFilfilled函数,主要是为了能够捕捉抛出的错误
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
});
}
// next函数
function next(ret) {
if (ret.done) return resolve(ret.value); // 检查是否为最后一步
var value = toPromise.call(ctx, ret.value); // 确保每一步的返回值是Promise对象
if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 使用then方法为返回值加上回调函数,后通过onFulfilled函数再次调用next函数
// 参数不符合要求时将Promise对象状态改为rejected,从而终止
return onRejected(
new TypeError(
'You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "'
+ String(ret.value)
+ '"'
)
);
}
处理并发的异步操作
co支持并发的异步操作,即允许某些操作同时进行,等到全部完成才进行下一步。