function* gen(p) {
var a = yield p + 1; //1
var b = yield p + 2; //2
return b; //3
}
var g = gen(1);
g.next(); //{value: 2, done: false}
g.next(); //{value: 3, done: false}
g.next(); //{value: undefined, done: true}
通过 var g = gen(1);
仅仅是创建了一个迭代器,函数 gen
里面的内容并没有执行函数体的执行时由第一个 g.next();
开始的 并且将 yield
所在那那条语句执行完后就会返回结果。而后面的语句并没有执行。返回值是一个对象,它的第一个属性是 yield 后面表达式的值 ( p+1
或者p+2
的值);第二个属性表示Generator函数是否执行完成。这里我们通过 yield
执行权限交出去,通过 next
将权限返回。
function* gen(p) {
var a = yield p + 1; //1
var b = yield a + 1; //2 注意这里是用到了 a
return b;
}
var g = gen(1);
g.next(); //{value: 2, done: false}
g.next(); //{value: NaN, done: false} 这里的值是 NaN
g.next(); //{value: undefined, done: true}
g.next(); //{value: 2, done: false}
g.next(2); //{value: 3, done: false}
g.next(6); //{value: 6, done: true}
注意这里 //1 处 //2 处 var a = yield p + 1;
这条赋值语句中 a
的值并不是 p + 1
的值。这条语句只是一种写法,这里 a
的值是我们在第二个 next
中传入的 2
这个很重要 b
的值也是我们在第三个 next
中传入的 6
由上面的内容我们总结 3 个关于 Generator 的重要特性
1 通过 yield
交出执行权限,通过 next
返回执行权限
2 调用 next
会得到一个返回值,这个值里面包含了 yield
后面的表达式的执行结果
3 我们可以通过给 next
传递参数,并且可以在 Generator 函数中通过上面所写的特殊方式来引用
function post(url, callback) {
setTimeout(function() {
var data = { //模拟异步处理结果
url:url,
value:10
};
callback(data);
}, 1000);
}
post('http://_ivenj',function(data){
console.log(data.url); // http://_ivenj
console.log(data.value); //10
});
function* gen(url) {
var data = yield post(url); //1
console.log(data.url);
console.log(data.value);
}
var g = gen('http://_ivenj');
var resultG = g.next();
g.next(resultG.value);
是的,这样写漂亮多了,很像 java
的同步写法。不同之处就是多了个 yield
和 *
,这个无伤大雅。当然以上这样用肯定是不行的。因为 post
毕竟是个异步方法。没有返回值.如果不能实现这样的写法我这半天就是在扯淡,所以通过包装是可以实现的。
(1)我有一篇文章 react 实践之 redux applyMiddleware方法详解 中介绍了柯里化(Currying)这篇文章虽然是写react的但是柯里化是独立的,这里就要利用柯里化的思想
(2)我们要在回调中调用 next
来继续执行,(这里有人会想不是不用回调了么,怎么还用,请继续看。。。)
post
的调用形式进行包装function kPost(url) {
return function(callback) {
post(url, callback);
}
}
kPost
就会同步的得到一个返回值function* gen(url) {
var data = yield kPost(url); //1
console.log(data.url);
console.log(data.value);
}
//这里执行方式会不同
var g = gen('http://_ivenj');
//启动任务
var resultG1 = g.next();
var value_resultG1 = resultG1.value; //resultG1.value 一定是一个函数,因为我们包装了
value_resultG1(function(data){
g.next(data); //通过在异步的回调中调用 next 并传递值来确保依赖异步结果的代码能正确执行
});
function post(url, callback) {
setTimeout(function() {
var data = { //模拟异步处理结果
url:url,
value:10
};
callback(data);
}, 1000);
}
function kPost(url) {
return function(callback) {
post(url, callback);
}
}
function* gen(url) {
var data = yield kPost(url); //1
console.log(data.url);
console.log(data.value);
}
//这里执行方式会不同
var g = gen('http://_ivenj');
//启动任务
var resultG1 = g.next();
var value_resultG1 = resultG1.value; //resultG1.value 一定是一个函数,因为我们包装了
value_resultG1(function(data){
g.next(data);
});
value_resultG1(function(data){
g.next(data);
});
next
进行了结果的传递,这里面有共同之处,不管是哪一种异步,我们都只传递值。大家的处理都是一样的。真正的业务逻辑确实是用同步的方式写的。那么,我们可以将共同的地方提取出来,写一个通用的函数去执行这个传值操作,这样,我们完全就告别了异步,再也看不到了,好开心。 co.js
就是一个这种 generator
的执行库。使用它是我们只需要将我们的 gen 传递给它像这样 co(gen)
是的就这样。下面我们自己写一个 co
function co(taskDef) {
//获取迭代器 类似 java 中的外柄迭代子
var task = taskDef();
//开始任务
var result = task.next();
//调用next的递归函数
function step() {
if (!result.done) { //如果generator没有执行完
if (typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
result = task.throw(err);
return;
}
result = task.next(data); //向后传递当前异步处理结果
step(); //递归执行
});
} else {
result = task.next(result.value); //如果执行完了就传递值
step(); //递归执行
}
}
}
// 启动递归函数
step();
}
function post(url, callback) {
setTimeout(function() {
var data = { //模拟异步处理结果
url:url,
value:10
};
callback(data);
}, 1000);
}
function kPost(url) {
return function(callback) {
post(url, callback);
}
}
function gen(url) {
return function* () {
var data = yield kPost(url); //1
console.log(data.url);
console.log(data.value);
}
}
function co(taskDef) {
var task = taskDef();
//开始任务
var result = task.next();
// 调用next的递归函数
function step() {
if (!result.done) { //如果generator没有执行完
if (typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
result = task.throw(err);
return;
}
result = task.next(data); //向后传递当前异步处理结果
step(); //递归执行
});
} else {
result = task.next(result.value); //如果执行完了就传递值
step(); //递归执行
}
}
}
// 启动递归函数
step();
}
co(gen('http://_ivenj')); //调用方式就是这么简单
Promise
也可以呢,答案是肯定的,柯里化的思想只是一种实现方式, Promise
也是一种,你可以自己去琢磨, co.js
就是将两种方式都实现了的一个执行器。 es7
中从语言层面对 Generator
进行了包装,在 es7
中我们可以使用 async
和 await
更优雅的实现类似java的顺序书写方式, async
和 await
是Generator
的语法糖,在 es7
中内置了执行器。别人都说是终极方案。