欢迎来到佳佳doubi小课堂~在这里你可以学到实用又错误的知识×。
好久没写正经的博文了,让我来正经一下。
使用Generator函数来创建你的异步函数,先说明一下原理。
Generator函数是ES6里的新函数类型,函数定义写作这样
1 2 3 4 |
function* a(){ … } |
看到没,在函数名和function之间多了一个*号,这不是我写错了,是Generator函数的标志,这个*号其实也可以贴着函数名写 function *a(){},也可以不加空格 function*a(){},不过还是推荐把它贴着function写,这样既不会影响一些编辑器里的高亮,也不会降低代码可读性。
那么为什么Generator函数可以用来做异步函数呢?
我们普通的函数都是顺序执行的,像这样
1 2 3 4 5 6 7 |
function a(){ console.log(1); console.log(2); console.log(3); } a(); |
会在控制台依次输出
1 2 3 |
1 2 3 |
当然这不能说明什么,不过可以留着和接下来的示例代码做对比。
Generator函数有一个和普通函数不同的地方,它里面可以出现一个叫做’yield’的关键字。
yield关键字可以让函数执行到它前面以后暂停,飞出当前的执行环境去做其他事,当这个Generator的next()方法被调用的时候,js又会回到这个函数环境里,继续执行代码。
注:关于’yield前面’的解释
yield前面指的并不是带有yield这个关键字的语句的前面一句,而是跟在yield关键字后面的语句。在Generator函数被用来做异步函数时yield一般都会以这样的形式出现:
12345 function* a(){console.log(1);let result=yield 一个异步函数;console.log(2);}这个异步函数指的就是yield的前面,因为它的执行顺序是到那个异步函数之后就暂停,当Generator的next被调用的时候此处的yield就会获取到next函数的参数,然后赋值给result变量,然后接着执行下去。当然,如果yield单独成句的话,那就相当于执行到前一句暂停了。
上面的注释可能打得太早了,你如果原本不知道Generator是什么的话可能会看不懂,不过你可以先往下看,然后再倒上来看一次。
Generator函数的使用方法要先说明一下,虽然在这篇文章里这不是主要的,我完全可以直接丢个代码出来说说怎么写个异步函数就可以完事了,不过本着提高误导教程的biger的精神,我决定写详细一些。
Generator的定义上面说过了
1 2 3 4 5 6 7 8 9 10 11 |
function* a(){ //你的代码 //我还是负责地写个例子吧,不过这里的例子先不涉及异步,不过会体现一下yield的各种写法 var i=123; console.log(i); yield console.log(234);//输出234后暂停 let iWillGetAParameter=yield;//直接暂停,等待next传来的参数 console.log(iWillGetAParameter); yield;//无意义的暂停 yield;//同上 } |
Generator的调用(这里的代码接着上面的环境)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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,接着就会输出这个字符串了 //下面还有两个没意义的暂停我就不继续调用了 |
以上代码的过程可以解释如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
生成Generator:fun Generator进入待续状态 调用next 定义变量i=123 输出i (123) 输出234 暂停 调用next 暂停 调用next('Your parameter’) yield丢出传入的参数 iWillGetAParameter接收到参数 输出iWillGetAParameter 暂停 |
可以看到,正是因为可以随意出入函数环境的特性,使Generator可以用来制作自己的异步函数。
相比使用setTimeout来模拟异步的好处就是可以保持函数的变量环境,不用让setTimeout带着一大堆参数一起跑了。
首先,我要定义一个特别的函数(这是我的做法,实际做法多种多样)
1 2 3 4 5 |
runasync=function(gen){ let g=gen(function(){g.next(arguments);}); g.next(); return g; } |
它是啥?它是用来帮我们来调用next函数的函数,把要异步执行的过程丢给它,然后函数就会自己异步执行起来,而不用我们来一个个调用next了。
然后可以开始制作我们可爱的异步函数啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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主进程里执行的,更准确的说这应该叫协程,可以算是个模拟的多线程。不过也没关系啦,反正都这么写了。。。不太讲究的话其实表面来看是一个意思。
然后执行之,测试异步函数的话,一个个执行肯定是没有意思的,所以要同时调用多个来测试,于是就这样
1 2 3 4 5 |
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 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
にゃ! にゃ! にゃ! にゃ! にゃ! 萌え佳佳 萌え佳佳 萌え佳佳 萌え佳佳 萌え佳佳 不服上面的话? 不服上面的话? 不服上面的话? 不服上面的话? 不服上面的话? 不服来战! 不服来战! 不服来战! 不服来战! 不服来战! 我是一开始定义的字符串 1执行完啦! 我是一开始定义的字符串 2执行完啦! 我是一开始定义的字符串 3执行完啦! 我是一开始定义的字符串 4执行完啦! 我是一开始定义的字符串 5执行完啦! |
可以看出这5个调用全部被拆分了,而不是一个执行完再执行另外一个。而且函数内部环境是一直保存到执行结束的。
===================================================
我知道你们最喜欢例子,来来来,往这看
- 在浏览器里支持像nextTick这种空闲时调用函数的方法也有来着,不过我忘了,而且支持的浏览器似乎也很有限。所以在浏览器环境中使用这种方法的可能不多,如果你想,可以用setTimeout来调用letmenext,一样可以达到这样的效果。
- 还有其它任何异步函数都可以用来在callback的位置传递letmenext(要注意,传递letmenext作为回调的函数必须确保只会执行它一次,否则会扰乱运行结构)。
- 也可以用这种异步方法来做动画效果,搭配定时函数,可以把动画节点依次写下去,配合css变换。而不用一个个setTimeout嵌套下去,变成一团难看的代码。
- 说到动画效果,requestAnimationFrame函数也可以用来触发,不过受限于屏幕刷新率和绘图速度。
还有一个用法就是可以把一系列异步函数写在一排里,而不用层层嵌套。代码好看多了喵! - 一般这样的伪多线程模式用在会耗时很久的函数上,防止阻塞其它事务。在函数里适当的地方放一个暂停即可异步之。
各种异步函数使用方法自己研究去吧,我才不会再写一个例子出来呢CwC。
参考:
本文发布于 https://luojia.me
本站文章未经文下加注授权不得拷贝发布。
本博客使用Disqus评论系统,如果看不到评论框,请尝试爬墙。