Promise,Generator,Await/Async
上节中忘记讲:Iterator接口和Generator函数的关系了,Symbol.iterator方法的最简单的实现就是通过Generator函数:
let myIterable = { [Symbol.iterator]:function* (){ yield 1; yield 2; yield 3; } } let aa = [...myIterable]; aa //[1, 2, 3]
用Generator函数写Symbol.iterator方法几乎不用部署任何代码,只要yield命令给出每一步的返回值即可。接下来开始写Generator函数吧,貌似没有promise好用,看下图,Await/Async函数是最腻害的,所以掌握最强的方法,你就可以所向披靡了。
在浏览知乎的中一个有关node.js是用来做什么的时候,看到有关ES6的新特性,然后就写个博客吧!
Part One:promise
Part Two:Generator
Generator函数是个状态机,封装多个内部状态。执行Generator函数会返回一个遍历器对象。
function* hello(){ yield 'hello'; yield 'world'; return 'ending'; } var hw = hello();
hw.next(); //{value: "hello", done: false}
hw.next() //{value: "world", done: false}
hw.next() //{value: "ending", done: true}
hw.next() //{value: undefined, done: true}
这里定义了一个generator函数,变量hw是一个指向hello函数内部状态的指针对象。函数的调用必须调用遍历器对象的next方法,使得指正移向下一个状态。调用next方法时候,内部指针从函数头部或者上一次停下来的地方开始执行。Generator函数是分段执行的,yield表达式是暂停执行的标记,next是恢复执行。
yield表达式:
在Generator函数内部,yield表达式只是暂停的标识。
yield和return的相似之处在于都能返回紧跟在语句后面的表达式的值,区别在于每次遇到yeild,函数暂停执行,下一次再从该位置继续向后执行。return不具备位置记忆的功能。Generator函数可以返回一系列的值,因为内部可以有多个yield。(Generator在英文中是“生成器”的意思)
如果Generator内部不用yield,就变成了一个单纯的暂缓执行的函数。
yield只能用在Generator函数内部,其他地方是会报错的!!!
在用for/of进行遍历Generator时候,不需要再调用next方法,例如最上面的例子:Generator和Iterator接口的关系时,举个栗子:
function* foo(){ yield 1; yield 2; yield 3; return 4; } for(var i of foo()){ console.log(i) } //1 2 3
一旦next方法的返回对象的done属性为true时,for/of循环就会中止,且不包含返回值,所以上面的return语句返回的4不包含在循环中。
这里使用Generator函数写了一个斐波那契数列的方法,遍历时候将小于1000的数字打印出来:
function* fibonacci(){ let [prev,curr] = [0,1]; for(;;){ [prev,curr] = [curr,prev+curr]; yield curr; } } for(let n of fibonacci()){ if(n>1000) break; console.log(n); }
如何遍历一个没有Iterator接口的对象呢?上一节中给对象加上Symbol.iterator属性就可以,而在Generator当中呢?
function* objectEntires(obj){ let propKeys = Reflect.ownKeys(obj); for(let propKey of propKeys){ yield [propKey,obj[propKey]]; } } let jane = {first: 'Jane',last: 'Ostin'}; for(let [key,value] of objectEntires(jane)){ console.log(`${key}->${value}`) } VM4211:11 first->Jane VM4211:11 last->Ostin
或者
函数当中涉及到Reflect,有时间写一篇有关Reflect的博文~~~
拓展运算符(...),解构赋值和Array.from()等都是遍历器接口,可以将Generator函数作为参数。
function* numbers(){ yield 1; yield 2; return 3; yield 4; } let aa = [...numbers()] let bb = Array.from(numbers()); let [x,y] = numbers(); aa //[1, 2] bb //[1, 2] x //1 y //2
这里有个需要特别注意的点:一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了,如果还调用next方法,value就是undefined,done就是true了。即JavaScript引擎认为这个Generator已经运行结束了。
注意:在Generator内部调用一个Generator函数是不会起作用的,所以就需要用到yield*表达式,用于在一个Generator函数 里面执行另外一个Generator函数。
function* foo(){ yield 'a'; yield 'b'; } function* bar(){ yield 'x'; yield* foo(); yield 'y'; } [...bar()] //["x", "a", "b", "y"]
如果不用yield* 而使用yield后面跟一个Generator函数的话,返回的就是一个遍历器对象了。在使用yield*时候(Generator里面没有return语句),就类似于for/of的简写形式。在有return语句时候呢?
任何数据结构只要有Iterator接口,就可以被yield*遍历。
let read = (function* (){ yield 'hello'; yield* 'hello'; }()) [...read] //["hello", "h", "e", "l", "l", "o"]
function* AA(){ yield 1; return 2; } function* BB(){ yield 3; let re = yield* AA(); console.log(re) yield 4; } [...BB()] //2 // [3, 1, 4]
在运行内部有return的Generator函数时候,如果将它赋值为一个变量指针,则打印结果是会有return的结果输出。
出个题:如果遍历二维数组?
const tree = ['a',['b','c'],['d','e']]; function* iterTree(arr){ for(let item of arr){ if(Array.isArray(item)){ yield* iterTree(item); }else{ yield item; } } } [...iterTree(tree)] // ["a", "b", "c", "d", "e"]
如果对象的属性是Generator函数,可以进行简写:
let obj = { * myMethod(){ yield 1; yield 2; } } [...obj.myMethod()] //[1, 2] 类似于==》 let obj = { myMethod:function* (){ yield 1; yield 2; } } [...obj.myMethod()] //[1, 2]
注意:Generator函数和普通构造函数的区别?
相同点:实例化后的Generator函数对象,会继承Generator函数的原型上的方法,这点跟构造函数蕾西。
区别:Generator函数返回的是遍历器对象,而不是this对象,所以函数上的固有方法,实例是不继承的,也不能new一个Generator对象,会报错。
如何将Generator函数返回一个正常的实例对象,既可以使用next方法,也可以获得正常的this对象?例如:F是一个Generator函数,可以将F内部的this对象绑定obj对象,然后调用next方法,返回Iterator对象,给obj上面添加属性,将所有内部的属性都绑定在obj对象上面,因此obj对象也就是F的实例了。
如何将obj和f统一?
可以将F的this指向它自己的原型对象。如下:
function* F(){ this.a = 1; yield this.b = 2; yield this.c = 3; } var f = F.call(F.prototype); f.next(); f.next(); f.next(); console.log(f.a,f.b,f.c); //1,2,3
就是将Generator函数里面的this指向了它本身的原型对象上面,在调用了next方法之后给原型上面添加属性。
如何让f是可以用构造函数new出来的对象,还可以使用Generator函数的next方法?
function* F(){ this.a = 1; yield this.b = 2; yield this.c = 3; } function gen(){ return F.call(F.prototype); } var f = new gen(); f.next(); f.next(); f.next(); console.log(f.a,f.b,f.c); VM910:14 1 2 3
就是真正的再新建一个构造函数,里面return F的实例,然后就可以new一个对象并使用Generator的方法,还有原型上面的属性了。
用Generator函数 实现一个状态机就更加简洁化,而且比较安全(状态不会给非法篡改),更加符合函数式编程的思想。
var clock = function* (){ while(true){ console.log("Tick!"); yield; console.log("Tock!") yield; } } var c = clock(); c.next() //Tick! c.next() //Tock! c.next() //Tick!
Generator可以暂停函数执行,返回任意表达式的值,应用场景:
1,处理异步操作,改写回调函数
2,可以通过Generator函数逐步读取文本文件
3,控制流管理,多部操作非常耗时,采用回调函数的方式,函数嵌套的模式。如果是promise的话,可能是回调在then里面的嵌套,如果用Generator函数的话:
function scheduler(task){ var taskObj = task.next(task.value); if(!taskObj.done){ task.value = taskObj.value; scheduler(task); } } function* longRunningTask(v1){ try{ var v2 = yield step1(v1); var v3 = yield step2(v2); var v4 = yield step3(v3); var v5 = yield step4(v4); }catch(e){ console.log(`ERR${e}`) } } scheduler(longRunningTask(initialValule))
那么异步的处理方法呢??下一节讲Await/Async方法,本节的Generator讲太多,篇幅过长~~~