Generator函数与异步编程
Posted Web手艺人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Generator函数与异步编程相关的知识,希望对你有一定的参考价值。
一、Generator函数
1、简介
ECMScript2015标准推出已有3年时间了,各类教程中对Generator函数的讨论已经很多,本篇文章只做简要介绍,仅对Generator在异步编程中的表现做讨论,如果对Generator还不熟悉的话,可要抓紧时间补补功课了哟~
Generator函数的出现使我们第一次打破了js语言运行时的函数完整性,使函数可以交出执行权,从而达到暂停执行的目的。那么Generator函数会在哪里暂停执行呢。
2、yield语句(表达式)
执行Generator函数会返回一个迭代器,第一次调用迭代器的next()的方法来启动函数并在第二个yield语句处暂停。再次调用next()方法可以从第二个yield处开始处继续执行函数,在第三个yield处暂停,依次类推直到整个函数执行完毕。考虑如下代码:
function *foo(x) {
var y = x * (yield)
}
var it = foo(6);
it.next();
var res = it.next(7);
res.value; // 42
如果没有读过Generator相关概念,这段代码看起来可能有点怪,为什么x会乘一个语句,(yield是有返回值的,所以有的教程里会叫表达式,到底叫什么,我们这里不纠结,毕竟这不是重点)这就是Generator最牛逼的特点了,函数在执行过程中依然可以进行输入和输出。
3、双向通信
刚才我们说到x * 后面的yield语句有一个返回值,聪明的你可能已经猜到了那句it.next(7)就是这个yiled语句返回值(6*7=42 ^_^),这个7就是由it.next()函数传入的。继续考虑如下代码:
function *foo(){
var y = x * (yield ‘helloworld’);
return y;
}
var it = foo(6);
var res = it.next(); //不带参数
res.value; //helloworld
res = it.next(7);
res.value;//42
我们看到第一个it.next()没有带任何参数,它的作用是启动foo函数,同时在这提出一个问题,foo函数要把什么值传给我?那么谁来回答这个问题呢?就是第一个yield语句了,所以我们可以看到,第一次输出res.value的值是helloworld。yield在向函数外抛出结果的同时也提出了一个问题:我要把什么值放在这个表达式里?回答这个问题的就是下一个it.next(7),如你所愿,变量y的值为42。
这里似乎没有第二个yield来回答第二个yield的问题了,别担心,最后的
return语句完成了这个任务。
二、使用Generator函数异步编程
有了前面的基础,来理解Generator在异步编程中的作用就容易的多了,上一节我们提到,可以利用yield语句和next方法进行双向通信,可以利用yield语句暂停Generator函数的执行,直到下一个next方法执行时恢复。基于以上两点我们很容易的想到,类似ajax请求这样的异步编程是不是可以利用yield阻塞函数,直到某个地方调用了next()将请求传进来。形如下面的写法
function *foo(url) {
var res = yield ajax(url);
}
答案是肯定的,考虑如下代码
function getData(url) { ajax(url, function(data) {
if(data.errno!==0){ it.throw(errmsg) } else { it.next(data) } }); }
function *main(){
try { var text = yield getData('api/data'); } catch (err) {
console.log(err)
} } var it = main(); it.next();//启动!
// 这里暂时忽略
try {
it.next(); } catch (err) {
console.log(err) }
首先定义了一个getData函数来异步获取数据,再定义一个main函数作为我们的主函数。通过第一个it.next()启动后,函数在yield处暂停,在getData方法的ajax回调中再次执行it.next将请求结果传入并恢复执行。
你可能已经注意到我在上述代码中加入了try、catch,这就是Generator另一个强大之处,传统的ajax回调是无法直接进行错误传递的,或者说想实现同样的功能需要维护一套颇具复杂性的代码。迭代器提供了throw方法可以将外部的异常直接传入内部。那么既然消息可以双向传递,异常也可以双向传递,回头看上面暂时忽略部分的代码,当Generator函数内部发生异常时,对外部迭代器try catch也是可以拿到的。就是一个,叼。
三、Generator与Promise结合
Generator与Promise结合可以说是ES6阶段最强大的异步编程方法,考虑如下代码:
function getData(url){
return fetch(url);// fetch返回一个promise
}
function *main(){
try {
var res = yield getData('api/data')
} catch(err) {
console.log(err)
}
}
var it = main();
var p = it.next().value;
p.then(res => {
it.next(res)
},rej => {
it.throw(err)
});
乍一看好像没什么牛逼的地方,不就是callback变成了promise么。如果Generator函数可以自动执行,不需要我们手动next,那才牛逼了。
Generator函数的自动执行器开源的方案有很多,其中最著名的是由TJ发布的co,node框架koa更是将中间件强制规定为Generator函数并自动执行。co 模块的思路就是利用 generator 的这个特性,将异步操作跟在 yield 后面,当异步操作完成并返回结果后,再触发下一次 next() 。当然,跟在 yield 后面的异步操作需要遵循一定的规范 thunks 和 promises。利用co我们可以写出如下的代码来自动执行Generato函数
co(
function * () {
var file1 = yield readFile('public.pem');
console.log(file1.toString("base64"));
});
co的源码在这就不介绍了,大家可以上github上自行学习。
以上是关于Generator函数与异步编程的主要内容,如果未能解决你的问题,请参考以下文章