[Javascript]使用Generator函数创建你自己的异步函数

欢迎来到佳佳doubi小课堂~在这里你可以学到实用又错误的知识×。

好久没写正经的博文了,让我来正经一下。

使用Generator函数来创建你的异步函数,先说明一下原理。

Generator函数是ES6里的新函数类型,函数定义写作这样

function* a(){
	…
}

看到没,在函数名和function之间多了一个*号,这不是我写错了,是Generator函数的标志,这个*号其实也可以贴着函数名写`function *a(){}`,也可以不加空格`function*a(){}`,不过还是推荐把它贴着function写,这样既不会影响一些编辑器里的高亮,也不会降低代码可读性。

那么为什么Generator函数可以用来做异步函数呢?

我们普通的函数都是顺序执行的,像这样

function a(){
	console.log(1);
	console.log(2);
	console.log(3);
}
a();

会在控制台依次输出

1
2
3

当然这不能说明什么,不过可以留着和接下来的示例代码做对比。

 

Generator函数有一个和普通函数不同的地方,它里面可以出现一个叫做’yield’的关键字。
yield关键字可以让函数执行到它前面以后暂停,飞出当前的执行环境去做其他事,当这个Generator的next()方法被调用的时候,js又会回到这个函数环境里,继续执行代码。

注:关于’yield前面’的解释
yield前面指的并不是带有yield这个关键字的语句的前面一句,而是跟在yield关键字后面的语句。在Generator函数被用来做异步函数时yield一般都会以这样的形式出现:

function* a(){
	console.log(1);
	let result=yield 一个异步函数;
	console.log(2);
}

这个异步函数指的就是yield的前面,因为它的执行顺序是到那个异步函数之后就暂停,当Generator的next被调用的时候此处的yield就会获取到next函数的参数,然后赋值给result变量,然后接着执行下去。当然,如果yield单独成句的话,那就相当于执行到前一句暂停了。

上面的注释可能打得太早了,你如果原本不知道Generator是什么的话可能会看不懂,不过你可以先往下看,然后再倒上来看一次。

Generator函数的使用方法要先说明一下,虽然在这篇文章里这不是主要的,我完全可以直接丢个代码出来说说怎么写个异步函数就可以完事了,不过本着提高误导教程的biger的精神,我决定写详细一些。

Generator的定义上面说过了

function* a(){
	//你的代码
	//我还是负责地写个例子吧,不过这里的例子先不涉及异步,不过会体现一下yield的各种写法
	var i=123;
	console.log(i);
	yield console.log(234);//输出234后暂停
	let iWillGetAParameter=yield;//直接暂停,等待next传来的参数
	console.log(iWillGetAParameter);
	yield;//无意义的暂停
	yield;//同上
}

Generator的调用(这里的代码接着上面的环境)

var fun=a();//注:这一句代码并不会执行a函数,而是使fun变量变成了一个Generator

fun.next();
//第一次执行next表示开始执行函数,所以带参数没有意义
//这一句会导致a执行到console.log(234)

fun.next();
//这句会使a执行到下一个yield前然后停下
//(在上面的代码里其实就是什么也没有执行,只是刚回去又被飞出来了)

fun.next('Your parameter');
//传入参数,next就像一个黑洞,把参数吞了进去,而它会立刻在暂停了函数的yield中吐出来,
//结果就被赋值给了yield前面的变量iWillGetAParameter,接着就会输出这个字符串了

//下面还有两个没意义的暂停我就不继续调用了

以上代码的过程可以解释如下

生成Generator:fun
Generator进入待续状态
调用next
定义变量i=123
输出i	(123)
输出234
暂停
调用next
暂停
调用next('Your parameter’)
yield丢出传入的参数
iWillGetAParameter接收到参数
输出iWillGetAParameter
暂停

可以看到,正是因为可以随意出入函数环境的特性,使Generator可以用来制作自己的异步函数。
相比使用setTimeout来模拟异步的好处就是可以保持函数的变量环境,不用让setTimeout带着一大堆参数一起跑了。

首先,我要定义一个特别的函数(这是我的做法,实际做法多种多样)

runasync=function(gen){
	let g=gen(function(){g.next(arguments);});
	g.next();
	return g;
}

它是啥?它是用来帮我们来调用next函数的函数,把要异步执行的过程丢给它,然后函数就会自己异步执行起来,而不用我们来一个个调用next了。

然后可以开始制作我们可爱的异步函数啦。

function papapa(pa,callback){//我怎么定义了一个普通函数?
	runasync(//实际上异步过程写在这里了,外面的函数只是一个套套而已
		function*(letmenext){//这里的letmenext,从上面的代码可以看出,是runasync函数丢进来的一个函数,用以调用next
			var t='我是一开始定义的字符串';
			console.log('にゃ!');
			yield process.nextTick(letmenext);
			//这里写的是node代码,process.nextTick是一个异步函数,用于在进程有空的时候调用传入的函数
			//(在这也就是在进程有空时间接调用了这个Generator的next函数,使之断续执行)
			console.log('萌え佳佳');
			yield process.nextTick(letmenext);
			console.log('不服上面的话?');
			yield process.nextTick(letmenext);
			console.log('不服来战!');
			yield process.nextTick(letmenext);
			console.log(t);
			callback(pa+'执行完啦!');//执行到结尾调用回调函数
		}
	);
}

老实说我写到这里开始有些后悔叫它异步函数了,因为其实所有过程还是在js主进程里执行的,更准确的说这应该叫协程,可以算是个模拟的多线程。不过也没关系啦,反正都这么写了。。。不太讲究的话其实表面来看是一个意思。

然后执行之,测试异步函数的话,一个个执行肯定是没有意思的,所以要同时调用多个来测试,于是就这样

papapa(1,function(t){console.log(t)});//为了区分各个调用,我给进了不同的数字作为第一个参数,第二个是’回调函数’,会在执行完之后调用
papapa(2,function(t){console.log(t)});
papapa(3,function(t){console.log(t)});
papapa(4,function(t){console.log(t)});
papapa(5,function(t){console.log(t)});

猜猜结果是什么?

在我的node里输出如下

にゃ!
にゃ!
にゃ!
にゃ!
にゃ!
萌え佳佳
萌え佳佳
萌え佳佳
萌え佳佳
萌え佳佳
不服上面的话?
不服上面的话?
不服上面的话?
不服上面的话?
不服上面的话?
不服来战!
不服来战!
不服来战!
不服来战!
不服来战!
我是一开始定义的字符串
1执行完啦!
我是一开始定义的字符串
2执行完啦!
我是一开始定义的字符串
3执行完啦!
我是一开始定义的字符串
4执行完啦!
我是一开始定义的字符串
5执行完啦!

可以看出这5个调用全部被拆分了,而不是一个执行完再执行另外一个。而且函数内部环境是一直保存到执行结束的。

===================================================
我知道你们最喜欢例子,来来来,往这看

  • 在浏览器里支持像nextTick这种空闲时调用函数的方法也有来着,不过我忘了,而且支持的浏览器似乎也很有限。所以在浏览器环境中使用这种方法的可能不多,如果你想,可以用setTimeout来调用letmenext,一样可以达到这样的效果。
  • 还有其它任何异步函数都可以用来在callback的位置传递letmenext(要注意,传递letmenext作为回调的函数必须确保只会执行它一次,否则会扰乱运行结构)。
  • 也可以用这种异步方法来做动画效果,搭配定时函数,可以把动画节点依次写下去,配合css变换。而不用一个个setTimeout嵌套下去,变成一团难看的代码。
  • 说到动画效果,requestAnimationFrame函数也可以用来触发,不过受限于屏幕刷新率和绘图速度。
    还有一个用法就是可以把一系列异步函数写在一排里,而不用层层嵌套。代码好看多了喵!
  • 一般这样的伪多线程模式用在会耗时很久的函数上,防止阻塞其它事务。在函数里适当的地方放一个暂停即可异步之。

各种异步函数使用方法自己研究去吧,我才不会再写一个例子出来呢CwC。

参考:



本文发布于 https://luojia.me

本站文章未经文下加注授权不得拷贝发布。

0 0 投票数
打分
订阅评论
提醒
guest
5 评论
内联反馈
查看所有评论
Mooction
游客
9 年 前

PHP盲路过

Mooction
游客
9 年 前
回复给  Mooction

兼JS盲

Mooction
游客
9 年 前
回复给  罗佳(博主)

咳,说好的人艰不拆呢~~

食用菌C
游客
9 年 前
回复给  罗佳(博主)

还不快去复习~以及祝高考顺利w