iterable vs. iterator
generator function每次调用都会产生一个新的iterator实例,该iterator同时也是iterable,含有Symbol.iterator函数,基本上这个函数就是return this

generator 控制异步回调

在generator中yield一个异步操作,在该异步操作的回调中重启该generator. 在生成器内部我们实现了“用同步形式来调用异步操作”,而回调细节被封装隐藏起来。

  1. function foo(url){
  2. ajax(ur, (err,data)=>{
  3. if(err){
  4. it.throw(err)
  5. }else{
  6. it.next(data)
  7. }
  8. })
  9. }
  10. function *main(){
  11. try {
  12. const text = yield foo('http://example.com')
  13. console.log(text)
  14. }catch(err){
  15. console.error(err)
  16. }
  17. }
  18. const it = main();
  19. it.next()

这里yield返回的是undefined, 并没有给外界传递消息。注意,使用生成器可以达到“同步顺序”的目的,但失去了promise的可信任可组合功能。

ES6中最完美的世界就是生成器(看似同步的异步代码)和 Promise(可信任可组合)的结合

generator + Promise

将异步操作ajax用promise封装,并用yield返回这个promise. 这样我们就能很好地利用promise的功能. 与上面的代码相比,这里的generator是通过promise决议来重启的。

  1. function foo(url){
  2. return request(url) // request返回一个promise
  3. }
  4. function *main(){
  5. //与上面一样,无需修改
  6. }
  7. const it = main()
  8. const p = it.next()
  9. //等待promise决议
  10. p.then(function(text){
  11. it.next(text)
  12. }, function (err) {
  13. it.throw(err)
  14. })

前面的generator *main()都只有一个异步操作,如果有多个异步操作,手动驱动(启动/重启)该generator 会相当麻烦,最好是使用专门设计的工具(ES6未提供,很多Promise库提供)。以下为一个示例:

  1. //Benjamin Gruebaum(@benjamingr on GitHub)
  2. function run(gen){
  3. const args = [].slice.call(arguments, 1)
  4. const it = gen.apply(this, args)
  5. return Promise.resolve().then(funciton handleNext(value){
  6. const next = it.next(value)
  7. return (function handleResult(next){ //此次封装是为了将该函数实名化,以备后面调用
  8. if(next.done){
  9. return next.value;
  10. }else{
  11. return Promise.resolve(next.value).then(handleNext, function handleError(err){
  12. return Promise.resolve(it.throw(err)).then(handleResult) // 注意!
  13. })
  14. })(next);
  15. }
  16. })
  17. }

ES8中的async/await

async function总是返回一个promise, await 接受一个promise (thenable) 并(异步地)等待它返回。如果你await了一个Promise, async函数就会自动获知要做什么,它会暂停这个函数(就像生成器一样),直到promise决议。

生成器委托

yield *可以委托到任意的iterator

Promise vs. thunk

Promisify 与 thunkify 类似,本质上都是发出一个请求,并分别由返回的promise和thunk表示对这个请求的未来的答复。生成器中yield也可授受一个thunk, 只需要一个更智能的run()工具。thunk可看作promise的简化版,没有可信任和可组合性。

generator手工实现

generator可以通过函数闭包与状态机来实现:

  • 函数闭包可以保持多次调用的上下文
  • 状态机模拟每次调用的流程控制

考虑如下一个简单的generator

  1. function* foo(url) {
  2. try {
  3. console.log("requesting:", url)
  4. let val = yield request(url)
  5. console.log(val)
  6. } catch (err) {
  7. console.log("Oops:", err)
  8. return false
  9. }
  10. }

可以通过以下代码来手工实现,该实现可用于ES6之前,也展示了generator的实现原理。

  1. function foo(url) {
  2. let state
  3. let val
  4. function process(v) {
  5. switch (state) {
  6. case 1:
  7. console.log("requesting:", url)
  8. return request(url)
  9. case 2:
  10. val = v
  11. console.log(val)
  12. return;
  13. case 3:
  14. let err = v;
  15. console.log("Oops:", err)
  16. return false
  17. }
  18. }
  19. //构造并返回一个iterator
  20. return {
  21. next: function (v) {
  22. if (!state) {
  23. state = 1
  24. return {
  25. done: false
  26. value: process()
  27. }
  28. } else if (state == 1) {
  29. state = 2
  30. return {
  31. done: true
  32. value: process(v)
  33. }
  34. } else {
  35. return {
  36. done: true
  37. value: undefined
  38. }
  39. }
  40. },
  41. throw: function (e) {
  42. if(state==1){
  43. state = 3
  44. return {
  45. done: true
  46. value: process(e)
  47. }
  48. }else{
  49. throw e
  50. }
  51. }
  52. }
  53. }