[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-learning_es6_generator":3,"$fhhw4rhq3gjqs":25},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":16,"permalink":17,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"html":22,"excerpt":23,"cover":24},161,"article","learning_es6_generator","学习ES6生成器（Generator）","2013-12-29 13:35:00","web",[11,12,13,14,15],"ES2015","Generator","生成器","回调","异步","\n这几天，TJ大神的koa框架突然在国内火起来了，随之而来的，则是其使用的ES6生成器（Generator）引起了广大码农的强烈兴趣，各种文章也如雨后春笋般拔地而起，比如[这篇](https:\u002F\u002Fwww.imququ.com\u002Fpost\u002Fgenerator-function-in-es6.html)、[这篇](http:\u002F\u002Fbg.biedalian.com\u002F2013\u002F12\u002F21\u002Fharmony-generator.html)、还有[这篇](https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FJavaScript\u002FGuide\u002FIterators_and_Generators)。这个神奇的生成器被视为解决JS“回调恶魔金字塔”的利器。在动手实践之后，发现介绍ES6生成器的文章仍然有些疏漏，因此有了这篇文章，权当是对各位大大们的补充好了。\n\n## 背景\n\n在JS的使用场景中，异步操作的处理是一个不可回避的问题，如果不做任何抽象、组织，只是“跟着感觉走”，那么面对“按顺序发起3个ajax请求”的需求，很容易就能写出如下代码（假设已引入jQuery）：\n\n```javascript\n\u002F\u002F 第1个ajax请求\n$.ajax({\n  url:'http:\u002F\u002Fecho.113.im',\n  dateType:'json',\n  type:'get',\n  data:{\n    data:JSON.stringify({status:1,data:'hello world'}),\n    type:'json',\n    timeout:1000\n  },\n  success:function(data){\n    if(data.status === 1){\n      \u002F\u002F 第2个ajax请求\n      $.ajax({\n        ......此处省略500字\n        success:function(data){\n          if(data.status === 1){\n            \u002F\u002F 第3个ajax请求\n            $.ajax({\n              ......此处省略500字\n              success:function(data){\n                if(data.status === 1){\n\n                }\n              }\n            });\n          }\n        }\n      });\n    }\n  }\n});\n```\n\n当顺序执行的异步操作越来越多的时候，回调层级也就越多，这也就是传说中的“回调恶魔金字塔”。\n\n\u003C!-- more -->\n\n## 生成器的卢山真面目\n\n所谓“生成器”，其实是一个函数，但是这个函数的行为会比较特殊：\n\n1. 它并不直接执行逻辑，而是用来生成另一个对象（这也正是“生成器”的含义）\n2. 它所生成的对象中的函数可以把逻辑拆开来，一片一片调用执行，而不是像普通的函数，只能从头到尾一次执行完毕\n\n生成器的语法和普通函数类似，特殊之处在于：\n\n1. 字面量（函数声明\u002F函数表达式）的关键字`function`后面多了一个`*`，而且这个`*`前后允许有空白字符\n2. 函数体中多了`yield`运算符\n\n举个粟子：\n\n```javascript\nfunction * GenA(){\n  console.log('from GenA, first.');\n  yield 1;\n  console.log('from GenA, second.');\n  var value3 = yield 2;\n  console.log('from GenA, third.',value3);\n  return 3;\n}\n\nvar a = GenA();\n```\n\n接下来依次执行：\n\n```javascript\na.next();\n\u002F\u002F from GenA, first.\n\u002F\u002F Object {value:1,done:false}\n\na.next();\n\u002F\u002F from GenA, second.\n\u002F\u002F Object {value:2,done:false}\n\na.next(333);\n\u002F\u002F from GenA, third.\n\u002F\u002F 333\n\u002F\u002F Object {value:3,done:true}\n\na.next();\n\u002F\u002F Object {value:undefined,done:true}\n```\n\n这个例子反映了生成器的基本用法，有以下几点值得注意：\n\n1. 在调用`GenA()`时，函数体中的逻辑并不会执行（控制台没有输出），直接调用`a.next()`时才会执行\n2. `a`是一个对象，它由生成器`GenA()`调用而来，注意`GenA()`并没有返回`a`对象，这非常像构造函数的执行形式，但是不允许添加`new`\n3. 调用`a.next()`时，函数体中的逻辑才开始真正执行，每次调用时会到`yield`语句结束，并将`yield`的运算数作为结果返回\n4. `a.next()`返回的结果是一个对象，对`yield`的运算数做了包装，并带上了`done`属性\n5. 当`done`属性为`false`时，表示该函数逻辑还未执行完，可以调用`a.next()`继续执行\n6. 最后一次返回的结果为`return`语句返回的结果，且`done`值为`true`。如果不写`return`，则值为`undefined`\n7. `value3 = yield 2`这句是指，这一段逻辑返回2，在下一次调用`a.next()`时，将参数赋给value3。换句话说，这句只执行了后面半段就暂停了，等到再次调用`a.next()`时才会将参数赋给value3并继续执行下面的逻辑\n8. 返回值中`done`为`true`时，仍然可以继续调用，返回的值为`undefined`\n\n## 同步场景下生成器的使用\n\n来看看同步场景下，如何使用生成器：\n\n```javascript\nfunction * Square(){\n  for(var i=1;;i++){\n    yield i*i;\n  }\n}\n\nvar square = Square();\n\nsquare.next(); \u002F\u002F 1\nsquare.next(); \u002F\u002F 4\nsquare.next(); \u002F\u002F 9\n......\n```\n\n同步场景下大概就是这么用的，很无趣是吧？我也这么觉得，其实和直接函数调用差别不大。不过值得注意的是，我们在循环中并没有设中止条件，因为调用一个`square.next()`方法，它才会执行一次，不调用则不执行，所以不用担心死循环的问题。\n\n## 异步场景下的生成器使用\n\n如何用生成器解决异步场景下的“回调恶魔金字塔”呢？满心期待对吧，很遗憾，它并不能那么简单地解决……\n\n从前面的例子中，其实已经可以体会出来了，生成器的用法中并不包含对异步的处理，所以其实没有办法帮助我们对异步回调进行封闭。那么为什么大家将它视为解决回调嵌套的神器呢？在翻阅了不少资料后找到[这篇文章](http:\u002F\u002Fblog.stevensanderson.com\u002F2013\u002F12\u002F21\u002Fexperiments-with-koa-and-javascript-generators\u002F)，文章作者一开始也认为生成器并不能解决回调嵌套的问题，但下面自己做了解释，如果生成器的返回的是一系列的Promise对象的话，情况就会不一样了，举个粟子：\n\n```javascript\nfunction myAjax(){\n  return fetch('http:\u002F\u002Fecho.113.im?data=1');\n}\n```\n\n我们使用`window.fetch`方法来处理ajax请求，这个方法会返回一个Promise对象。然后，我们使用一个生成器来包装这个操作：\n\n```javascript\nfunction * MyLogic(){\n  var serverData = yield myAjax();\n  console.log('MyLogic after myAjax');\n  console.log('serverStatus:%s',serverData.status);\n}\n```\n\n使用的时候这样用：\n\n```javascript\nvar myLogic = MyLogic();\nvar promise = myLogic.next().value;\npromise.then(function(serverData){\n  myLogic.next(serverData);\n});\n```\n\n可以看到，我们这里的`myAjax1()`以及`MyLogic()`函数中，并没有使用回调，就完成了异步操作。\n\n这里有几个值得注意的点：\n\n1. `myAjax()`函数返回的是一个Promise对象\n2. `myLogic`中的第一个语句，返回给外界的是`myAjax()`返回的Promise对象，等外界再次调用`next()`方法时将数据传进来，赋值给`serverDate`\n3. `promise`的状态是由第三段代码，在外部进行处理，完成的时候调用`myLogic.next()`方法并将`serverData`再传回`MyLogic()`中\n\n你一定会问，下面这个`promise.done`不就是回调操作么？Bingo！这正是精华所在！我们来看一下这段代码做了什么：\n\n首先，`myLogic.next()`返回了一个Promise对象（`promise`），然后，`promise.then`中的回调函数所做的事情就是调用`myLogic.next()`方法就行了，除了调用`next()`方法，其它的什么事情都没有。此时，我们就会想到一个程序员特别喜欢的词，叫“封装”！既然这个回调函数只是调用`myLogic.next()`方法，那为什么不把它封装起来？\n\n## 异步封装\n\n首先，我们保持`myAjax()`和`MyLogic`定义不变，而将`myLogic.next()`放到一个函数来调用，这个函数专门负责调用`myLogic.next()`，得到返回的Promise对象，然后在Promise被resolve的时候再次调用`myLogic.next()`：\n\n```javascript\nvar myLogic = MyLogic();\n\nfunction genRunner(){\n\n  \u002F\u002F 调用next()获取promise\n  var yieldValue = myLogic.next();\n  var promise = yieldValue.value;\n\n  if(promise){\n    promise.then(function(data){\n      \u002F\u002F promise被resolve的时候再次调用genRunner\n      \u002F\u002F 以继续执行MyLogic中后面的逻辑\n      genRunner();\n    });\n  }\n}\n```\n\n这样我们就把不停地调用`myLogic.next()`和不停地`promise.then()`的过程进行了封装。运行`genRunner()`跑一下：\n\n```\nMyLogic after myAjax1\nUncaught (in promise) TypeError: Cannot read property 'status' of undefined(…)\n```\n\n可见`MyLogic`在`yield`后的语句的确被执行了，但是`serverData`却没有值，这是因为我们在调用`myLogic.next()`的时候没有把值传回去。稍微修改下代码：\n\n```javascript\n\u002F\u002F diff1: genRunner接受参数val\nfunction genRunner(val){\n\n  \u002F\u002F diff2: .next调用时把参数传过去，yield左边可以被赋值\n  var yieldValue = myLogic.next(val);\n  var promise = yieldValue.value;\n\n  if(promise){\n    promise.then(function(data){\n      \u002F\u002F diff3: 调用genRunner时传递参数\n      genRunner(data);\n    });\n  }\n}\n```\n\n这次一切都对了：\n\n```\nMyLogic after myAjax1\nserverStatus:200\n```\n\n至此我们已经把封装最核心的部分抽离出来了，我们的业务代码`MyLogic()`已经是“异步操作，同步写法”，而我们亲眼见证了这一切是怎么办到的。那么接下来？为什么不再封装得更通用一些呢？\n\n```javascript\nvar genRunner = function(GenFunc){\n\n  return new Promise(function(resolve, reject){\n\n    var gen = GenFunc();\n\n    var innerRun = function(val){\n\n      var val = gen.next(val);\n\n      \u002F\u002F 如果已经跑完了，则resolve\n      if(val.done){\n        resolve(val.value);\n        return;\n      }\n      \u002F\u002F 如果有返回值，则调用`.then`\n      \u002F\u002F 否则直接调用下一次innerRun()\n      \u002F\u002F 为简单起见，假设有值的时候永远是promise\n      if(val.value){\n        val.value.then(function(data){\n          innerRun(data);\n        });\n      }else{\n        innerRun(val.value);\n      }\n\n    }\n    innerRun();\n\n  });\n\n};\n```\n\n这里我们将刚刚看过的封装改成了`innerRun()`，并加上了自动调用。外面再封装了一层`genRunner()`，返回一个Promise。在`genFunc`全程调用完之后，Promise被resolve。\n\n用起来大约是这样：\n\n```javascript\ngenRunner(function*(){\n\n  var serverData = yield myAjax();\n  console.log('MyLogic after myAjax');\n  console.log('serverStatus:%s',serverData.status);\n\n}).then(function(message){\n\n  console.log(message);\n\n});\n```\n\n生活真美好！\n\n最后，以别人文章中的一段koa框架使用代码收尾吧：\n\n```javascript\nvar koa = require('koa'),\n  app = koa();\n\napp.use(function *() {\n\n  \u002F\u002F 这是这个例子中最重要的部分，我们进行了一系列异步操作，却没有回调\n  var city = yield geolocation.getCityAsync(this.req.ip);\n  var forecast = yield weather.getForecastAsync(city);\n\n  this.body = 'Today, ' + city + ' will be ' + forecast.temperature + ' degrees.';\n\n});\n\napp.listen(8080);\n```\n\n眼熟吗？koa就是像我们刚刚做的这样，封装了对生成器返回值的处理和调用`next()`方法的细节（这里的`app.use()`就像前面的`genRunner()`函数），使得我们的逻辑代码看起来是如此简单，这正是koa的伟大之处，也是ES6生成器这一特性能迅速引起如此多轰动的真正原因。\n","\u002Farticle\u002Flearning_es6_generator.html",null,0,"2026-08-28 04:37:17","published","\u003Cp>这几天，TJ大神的koa框架突然在国内火起来了，随之而来的，则是其使用的ES6生成器（Generator）引起了广大码农的强烈兴趣，各种文章也如雨后春笋般拔地而起，比如\u003Ca href=\"https:\u002F\u002Fwww.imququ.com\u002Fpost\u002Fgenerator-function-in-es6.html\">这篇\u003C\u002Fa>、\u003Ca href=\"http:\u002F\u002Fbg.biedalian.com\u002F2013\u002F12\u002F21\u002Fharmony-generator.html\">这篇\u003C\u002Fa>、还有\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FJavaScript\u002FGuide\u002FIterators_and_Generators\">这篇\u003C\u002Fa>。这个神奇的生成器被视为解决JS“回调恶魔金字塔”的利器。在动手实践之后，发现介绍ES6生成器的文章仍然有些疏漏，因此有了这篇文章，权当是对各位大大们的补充好了。\u003C\u002Fp>\n\u003Ch2>背景\u003C\u002Fh2>\n\u003Cp>在JS的使用场景中，异步操作的处理是一个不可回避的问题，如果不做任何抽象、组织，只是“跟着感觉走”，那么面对“按顺序发起3个ajax请求”的需求，很容易就能写出如下代码（假设已引入jQuery）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 第1个ajax请求\n$.ajax({\n  url:'http:\u002F\u002Fecho.113.im',\n  dateType:'json',\n  type:'get',\n  data:{\n    data:JSON.stringify({status:1,data:'hello world'}),\n    type:'json',\n    timeout:1000\n  },\n  success:function(data){\n    if(data.status === 1){\n      \u002F\u002F 第2个ajax请求\n      $.ajax({\n        ......此处省略500字\n        success:function(data){\n          if(data.status === 1){\n            \u002F\u002F 第3个ajax请求\n            $.ajax({\n              ......此处省略500字\n              success:function(data){\n                if(data.status === 1){\n\n                }\n              }\n            });\n          }\n        }\n      });\n    }\n  }\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>当顺序执行的异步操作越来越多的时候，回调层级也就越多，这也就是传说中的“回调恶魔金字塔”。\u003C\u002Fp>\n\u003Ch2>生成器的卢山真面目\u003C\u002Fh2>\n\u003Cp>所谓“生成器”，其实是一个函数，但是这个函数的行为会比较特殊：\u003C\u002Fp>\n\u003Col>\n\u003Cli>它并不直接执行逻辑，而是用来生成另一个对象（这也正是“生成器”的含义）\u003C\u002Fli>\n\u003Cli>它所生成的对象中的函数可以把逻辑拆开来，一片一片调用执行，而不是像普通的函数，只能从头到尾一次执行完毕\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>生成器的语法和普通函数类似，特殊之处在于：\u003C\u002Fp>\n\u003Col>\n\u003Cli>字面量（函数声明\u002F函数表达式）的关键字\u003Ccode>function\u003C\u002Fcode>后面多了一个\u003Ccode>*\u003C\u002Fcode>，而且这个\u003Ccode>*\u003C\u002Fcode>前后允许有空白字符\u003C\u002Fli>\n\u003Cli>函数体中多了\u003Ccode>yield\u003C\u002Fcode>运算符\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>举个粟子：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function * GenA(){\n  console.log('from GenA, first.');\n  yield 1;\n  console.log('from GenA, second.');\n  var value3 = yield 2;\n  console.log('from GenA, third.',value3);\n  return 3;\n}\n\nvar a = GenA();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>接下来依次执行：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>a.next();\n\u002F\u002F from GenA, first.\n\u002F\u002F Object {value:1,done:false}\n\na.next();\n\u002F\u002F from GenA, second.\n\u002F\u002F Object {value:2,done:false}\n\na.next(333);\n\u002F\u002F from GenA, third.\n\u002F\u002F 333\n\u002F\u002F Object {value:3,done:true}\n\na.next();\n\u002F\u002F Object {value:undefined,done:true}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这个例子反映了生成器的基本用法，有以下几点值得注意：\u003C\u002Fp>\n\u003Col>\n\u003Cli>在调用\u003Ccode>GenA()\u003C\u002Fcode>时，函数体中的逻辑并不会执行（控制台没有输出），直接调用\u003Ccode>a.next()\u003C\u002Fcode>时才会执行\u003C\u002Fli>\n\u003Cli>\u003Ccode>a\u003C\u002Fcode>是一个对象，它由生成器\u003Ccode>GenA()\u003C\u002Fcode>调用而来，注意\u003Ccode>GenA()\u003C\u002Fcode>并没有返回\u003Ccode>a\u003C\u002Fcode>对象，这非常像构造函数的执行形式，但是不允许添加\u003Ccode>new\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>调用\u003Ccode>a.next()\u003C\u002Fcode>时，函数体中的逻辑才开始真正执行，每次调用时会到\u003Ccode>yield\u003C\u002Fcode>语句结束，并将\u003Ccode>yield\u003C\u002Fcode>的运算数作为结果返回\u003C\u002Fli>\n\u003Cli>\u003Ccode>a.next()\u003C\u002Fcode>返回的结果是一个对象，对\u003Ccode>yield\u003C\u002Fcode>的运算数做了包装，并带上了\u003Ccode>done\u003C\u002Fcode>属性\u003C\u002Fli>\n\u003Cli>当\u003Ccode>done\u003C\u002Fcode>属性为\u003Ccode>false\u003C\u002Fcode>时，表示该函数逻辑还未执行完，可以调用\u003Ccode>a.next()\u003C\u002Fcode>继续执行\u003C\u002Fli>\n\u003Cli>最后一次返回的结果为\u003Ccode>return\u003C\u002Fcode>语句返回的结果，且\u003Ccode>done\u003C\u002Fcode>值为\u003Ccode>true\u003C\u002Fcode>。如果不写\u003Ccode>return\u003C\u002Fcode>，则值为\u003Ccode>undefined\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>value3 = yield 2\u003C\u002Fcode>这句是指，这一段逻辑返回2，在下一次调用\u003Ccode>a.next()\u003C\u002Fcode>时，将参数赋给value3。换句话说，这句只执行了后面半段就暂停了，等到再次调用\u003Ccode>a.next()\u003C\u002Fcode>时才会将参数赋给value3并继续执行下面的逻辑\u003C\u002Fli>\n\u003Cli>返回值中\u003Ccode>done\u003C\u002Fcode>为\u003Ccode>true\u003C\u002Fcode>时，仍然可以继续调用，返回的值为\u003Ccode>undefined\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>同步场景下生成器的使用\u003C\u002Fh2>\n\u003Cp>来看看同步场景下，如何使用生成器：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function * Square(){\n  for(var i=1;;i++){\n    yield i*i;\n  }\n}\n\nvar square = Square();\n\nsquare.next(); \u002F\u002F 1\nsquare.next(); \u002F\u002F 4\nsquare.next(); \u002F\u002F 9\n......\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同步场景下大概就是这么用的，很无趣是吧？我也这么觉得，其实和直接函数调用差别不大。不过值得注意的是，我们在循环中并没有设中止条件，因为调用一个\u003Ccode>square.next()\u003C\u002Fcode>方法，它才会执行一次，不调用则不执行，所以不用担心死循环的问题。\u003C\u002Fp>\n\u003Ch2>异步场景下的生成器使用\u003C\u002Fh2>\n\u003Cp>如何用生成器解决异步场景下的“回调恶魔金字塔”呢？满心期待对吧，很遗憾，它并不能那么简单地解决……\u003C\u002Fp>\n\u003Cp>从前面的例子中，其实已经可以体会出来了，生成器的用法中并不包含对异步的处理，所以其实没有办法帮助我们对异步回调进行封闭。那么为什么大家将它视为解决回调嵌套的神器呢？在翻阅了不少资料后找到\u003Ca href=\"http:\u002F\u002Fblog.stevensanderson.com\u002F2013\u002F12\u002F21\u002Fexperiments-with-koa-and-javascript-generators\u002F\">这篇文章\u003C\u002Fa>，文章作者一开始也认为生成器并不能解决回调嵌套的问题，但下面自己做了解释，如果生成器的返回的是一系列的Promise对象的话，情况就会不一样了，举个粟子：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function myAjax(){\n  return fetch('http:\u002F\u002Fecho.113.im?data=1');\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>我们使用\u003Ccode>window.fetch\u003C\u002Fcode>方法来处理ajax请求，这个方法会返回一个Promise对象。然后，我们使用一个生成器来包装这个操作：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>function * MyLogic(){\n  var serverData = yield myAjax();\n  console.log('MyLogic after myAjax');\n  console.log('serverStatus:%s',serverData.status);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>使用的时候这样用：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var myLogic = MyLogic();\nvar promise = myLogic.next().value;\npromise.then(function(serverData){\n  myLogic.next(serverData);\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>可以看到，我们这里的\u003Ccode>myAjax1()\u003C\u002Fcode>以及\u003Ccode>MyLogic()\u003C\u002Fcode>函数中，并没有使用回调，就完成了异步操作。\u003C\u002Fp>\n\u003Cp>这里有几个值得注意的点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Ccode>myAjax()\u003C\u002Fcode>函数返回的是一个Promise对象\u003C\u002Fli>\n\u003Cli>\u003Ccode>myLogic\u003C\u002Fcode>中的第一个语句，返回给外界的是\u003Ccode>myAjax()\u003C\u002Fcode>返回的Promise对象，等外界再次调用\u003Ccode>next()\u003C\u002Fcode>方法时将数据传进来，赋值给\u003Ccode>serverDate\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>promise\u003C\u002Fcode>的状态是由第三段代码，在外部进行处理，完成的时候调用\u003Ccode>myLogic.next()\u003C\u002Fcode>方法并将\u003Ccode>serverData\u003C\u002Fcode>再传回\u003Ccode>MyLogic()\u003C\u002Fcode>中\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>你一定会问，下面这个\u003Ccode>promise.done\u003C\u002Fcode>不就是回调操作么？Bingo！这正是精华所在！我们来看一下这段代码做了什么：\u003C\u002Fp>\n\u003Cp>首先，\u003Ccode>myLogic.next()\u003C\u002Fcode>返回了一个Promise对象（\u003Ccode>promise\u003C\u002Fcode>），然后，\u003Ccode>promise.then\u003C\u002Fcode>中的回调函数所做的事情就是调用\u003Ccode>myLogic.next()\u003C\u002Fcode>方法就行了，除了调用\u003Ccode>next()\u003C\u002Fcode>方法，其它的什么事情都没有。此时，我们就会想到一个程序员特别喜欢的词，叫“封装”！既然这个回调函数只是调用\u003Ccode>myLogic.next()\u003C\u002Fcode>方法，那为什么不把它封装起来？\u003C\u002Fp>\n\u003Ch2>异步封装\u003C\u002Fh2>\n\u003Cp>首先，我们保持\u003Ccode>myAjax()\u003C\u002Fcode>和\u003Ccode>MyLogic\u003C\u002Fcode>定义不变，而将\u003Ccode>myLogic.next()\u003C\u002Fcode>放到一个函数来调用，这个函数专门负责调用\u003Ccode>myLogic.next()\u003C\u002Fcode>，得到返回的Promise对象，然后在Promise被resolve的时候再次调用\u003Ccode>myLogic.next()\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var myLogic = MyLogic();\n\nfunction genRunner(){\n\n  \u002F\u002F 调用next()获取promise\n  var yieldValue = myLogic.next();\n  var promise = yieldValue.value;\n\n  if(promise){\n    promise.then(function(data){\n      \u002F\u002F promise被resolve的时候再次调用genRunner\n      \u002F\u002F 以继续执行MyLogic中后面的逻辑\n      genRunner();\n    });\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这样我们就把不停地调用\u003Ccode>myLogic.next()\u003C\u002Fcode>和不停地\u003Ccode>promise.then()\u003C\u002Fcode>的过程进行了封装。运行\u003Ccode>genRunner()\u003C\u002Fcode>跑一下：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>MyLogic after myAjax1\nUncaught (in promise) TypeError: Cannot read property 'status' of undefined(…)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>可见\u003Ccode>MyLogic\u003C\u002Fcode>在\u003Ccode>yield\u003C\u002Fcode>后的语句的确被执行了，但是\u003Ccode>serverData\u003C\u002Fcode>却没有值，这是因为我们在调用\u003Ccode>myLogic.next()\u003C\u002Fcode>的时候没有把值传回去。稍微修改下代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F diff1: genRunner接受参数val\nfunction genRunner(val){\n\n  \u002F\u002F diff2: .next调用时把参数传过去，yield左边可以被赋值\n  var yieldValue = myLogic.next(val);\n  var promise = yieldValue.value;\n\n  if(promise){\n    promise.then(function(data){\n      \u002F\u002F diff3: 调用genRunner时传递参数\n      genRunner(data);\n    });\n  }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这次一切都对了：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>MyLogic after myAjax1\nserverStatus:200\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>至此我们已经把封装最核心的部分抽离出来了，我们的业务代码\u003Ccode>MyLogic()\u003C\u002Fcode>已经是“异步操作，同步写法”，而我们亲眼见证了这一切是怎么办到的。那么接下来？为什么不再封装得更通用一些呢？\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var genRunner = function(GenFunc){\n\n  return new Promise(function(resolve, reject){\n\n    var gen = GenFunc();\n\n    var innerRun = function(val){\n\n      var val = gen.next(val);\n\n      \u002F\u002F 如果已经跑完了，则resolve\n      if(val.done){\n        resolve(val.value);\n        return;\n      }\n      \u002F\u002F 如果有返回值，则调用`.then`\n      \u002F\u002F 否则直接调用下一次innerRun()\n      \u002F\u002F 为简单起见，假设有值的时候永远是promise\n      if(val.value){\n        val.value.then(function(data){\n          innerRun(data);\n        });\n      }else{\n        innerRun(val.value);\n      }\n\n    }\n    innerRun();\n\n  });\n\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这里我们将刚刚看过的封装改成了\u003Ccode>innerRun()\u003C\u002Fcode>，并加上了自动调用。外面再封装了一层\u003Ccode>genRunner()\u003C\u002Fcode>，返回一个Promise。在\u003Ccode>genFunc\u003C\u002Fcode>全程调用完之后，Promise被resolve。\u003C\u002Fp>\n\u003Cp>用起来大约是这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>genRunner(function*(){\n\n  var serverData = yield myAjax();\n  console.log('MyLogic after myAjax');\n  console.log('serverStatus:%s',serverData.status);\n\n}).then(function(message){\n\n  console.log(message);\n\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>生活真美好！\u003C\u002Fp>\n\u003Cp>最后，以别人文章中的一段koa框架使用代码收尾吧：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var koa = require('koa'),\n  app = koa();\n\napp.use(function *() {\n\n  \u002F\u002F 这是这个例子中最重要的部分，我们进行了一系列异步操作，却没有回调\n  var city = yield geolocation.getCityAsync(this.req.ip);\n  var forecast = yield weather.getForecastAsync(city);\n\n  this.body = 'Today, ' + city + ' will be ' + forecast.temperature + ' degrees.';\n\n});\n\napp.listen(8080);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>眼熟吗？koa就是像我们刚刚做的这样，封装了对生成器返回值的处理和调用\u003Ccode>next()\u003C\u002Fcode>方法的细节（这里的\u003Ccode>app.use()\u003C\u002Fcode>就像前面的\u003Ccode>genRunner()\u003C\u002Fcode>函数），使得我们的逻辑代码看起来是如此简单，这正是koa的伟大之处，也是ES6生成器这一特性能迅速引起如此多轰动的真正原因。\u003C\u002Fp>\n","\u003Cp>这几天，TJ大神的koa框架突然在国内火起来了，随之而来的，则是其使用的ES6生成器（Generator）引起了广大码农的强烈兴趣，各种文章也如雨后春笋般拔地而起，比如\u003Ca href=\"https:\u002F\u002Fwww.imququ.com\u002Fpost\u002Fgenerator-function-in-es6.html\">这篇\u003C\u002Fa>、\u003Ca href=\"http:\u002F\u002Fbg.biedalian.com\u002F2013\u002F12\u002F21\u002Fharmony-generator.html\">这篇\u003C\u002Fa>、还有\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FJavaScript\u002FGuide\u002FIterators_and_Generators\">这篇\u003C\u002Fa>。这个神奇的生成器被视为解决JS“回调恶魔金字塔”的利器。在动手实践之后，发现介绍ES6生成器的文章仍然有些疏漏，因此有了这篇文章，权当是对各位大大们的补充好了。\u003C\u002Fp>\n\u003Ch2>背景\u003C\u002Fh2>\n\u003Cp>在JS的使用场景中，异步操作的处理是一个不可回避的问题，如果不做任何抽象、组织，只是“跟着感觉走”，那么面对“按顺序发起3个ajax请求”的需求，很容易就能写出如下代码（假设已引入jQuery）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 第1个ajax请求\n$.ajax({\n  url:'http:\u002F\u002Fecho.113.im',\n  dateType:'json',\n  type:'get',\n  data:{\n    data:JSON.stringify({status:1,data:'hello world'}),\n    type:'json',\n    timeout:1000\n  },\n  success:function(data){\n    if(data.status === 1){\n      \u002F\u002F 第2个ajax请求\n      $.ajax({\n        ......此处省略500字\n        success:function(data){\n          if(data.status === 1){\n            \u002F\u002F 第3个ajax请求\n            $.ajax({\n              ......此处省略500字\n              success:function(data){\n                if(data.status === 1){\n\n                }\n              }\n            });\n          }\n        }\n      });\n    }\n  }\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>当顺序执行的异步操作越来越多的时候，回调层级也就越多，这也就是传说中的“回调恶魔金字塔”。\u003C\u002Fp>\n","",{"total":26,"totalRoots":27,"comments":28,"pv":19},36,13,[29,36,42,48,52,58,62,66,72,76,80,84,88,92,98,102,106,110,116,120],{"id":30,"content":31,"user_id":32,"nick":33,"link":24,"date":34,"rid":19,"vote_up":19,"vote_down":19,"visible":35},73,"\u003Cp>学习了\u003C\u002Fp>",42,"吕耿敏","2014-01-02 15:54:54",true,{"id":37,"content":38,"user_id":39,"nick":40,"link":24,"date":41,"rid":30,"vote_up":19,"vote_down":19,"visible":35},74,"\u003Cp>唉，我还没写完。“被动异步”才是关键，而且这个Generator并不能解决异步回调的问题，被神化了。\u003C\u002Fp>",2,"TooBug","2014-01-03 01:54:08",{"id":43,"content":44,"user_id":45,"nick":46,"link":24,"date":47,"rid":37,"vote_up":19,"vote_down":19,"visible":35},75,"\u003Cp>坐等更新\u003C\u002Fp>",45,"Leo","2014-01-05 16:02:59",{"id":49,"content":50,"user_id":39,"nick":40,"link":24,"date":51,"rid":43,"vote_up":19,"vote_down":19,"visible":35},76,"\u003Cp>已更新，欢迎阅读。\u003C\u002Fp>","2014-01-21 05:35:01",{"id":53,"content":54,"user_id":55,"nick":56,"link":24,"date":57,"rid":19,"vote_up":19,"vote_down":19,"visible":35},77,"\u003Cp>好文。\u003C\u002Fp>",37,"basecss","2014-01-21 05:53:38",{"id":59,"content":60,"user_id":39,"nick":40,"link":24,"date":61,"rid":53,"vote_up":19,"vote_down":19,"visible":35},78,"\u003Cp>信不信我发完就知道你会回这两个字？\u003C\u002Fp>","2014-01-21 05:58:42",{"id":63,"content":64,"user_id":55,"nick":56,"link":24,"date":65,"rid":59,"vote_up":19,"vote_down":19,"visible":35},79,"\u003Cp>RT\u003C\u002Fp>","2014-01-21 06:00:51",{"id":67,"content":68,"user_id":69,"nick":70,"link":24,"date":71,"rid":19,"vote_up":19,"vote_down":19,"visible":35},80,"\u003Cp>赞. 请问博主为什么在讲这么深刻的技术的时候还能这么优雅地卖萌.\u003C\u002Fp>",46,"Json zhang","2014-01-21 06:02:16",{"id":73,"content":74,"user_id":69,"nick":70,"link":24,"date":75,"rid":67,"vote_up":19,"vote_down":19,"visible":35},81,"\u003Cp>我勒个大去评论写错了居然还有修改选项....\u003C\u002Fp>","2014-01-21 06:03:08",{"id":77,"content":78,"user_id":69,"nick":70,"link":24,"date":79,"rid":73,"vote_up":19,"vote_down":19,"visible":35},82,"\u003Cp>请收下我充满星星的眼神\u003C\u002Fp>","2014-01-21 06:03:34",{"id":81,"content":82,"user_id":39,"nick":40,"link":24,"date":83,"rid":77,"vote_up":19,"vote_down":19,"visible":35},83,"\u003Cp>哪里有卖萌？送的好不好，都没收你钱。\u003C\u002Fp>","2014-01-21 06:11:13",{"id":85,"content":86,"user_id":32,"nick":33,"link":24,"date":87,"rid":37,"vote_up":19,"vote_down":19,"visible":35},84,"\u003Cp>看完被动异步，koa中的generate封装的这么好，之前没完整版看完觉得还是挺遥远的东西，现在觉得这东西会火啊。代码本可以没有}}}}}}}\u003C\u002Fp>","2014-01-21 13:46:18",{"id":89,"content":90,"user_id":39,"nick":40,"link":24,"date":91,"rid":85,"vote_up":19,"vote_down":19,"visible":35},85,"\u003Cp>已经火了……\u003C\u002Fp>","2014-01-22 02:00:46",{"id":93,"content":94,"user_id":95,"nick":96,"link":24,"date":97,"rid":19,"vote_up":19,"vote_down":19,"visible":35},86,"\u003Cp>我Out了么……第一次知道Js居然有yield……原来是1.7新添加的。。。\u003C\u002Fp>",47,"Xana Hopper","2014-02-03 04:41:55",{"id":99,"content":100,"user_id":39,"nick":40,"link":24,"date":101,"rid":93,"vote_up":19,"vote_down":19,"visible":35},87,"\u003Cp>呃，不太清楚1.7是什么概念，不过这是ES6中还在讨论的特性，只在node最新版和chrome canary作了实现，而且需要手工开启这个特性。\u003C\u002Fp>","2014-02-03 11:46:32",{"id":103,"content":104,"user_id":95,"nick":96,"link":24,"date":105,"rid":99,"vote_up":19,"vote_down":19,"visible":35},88,"\u003Cp>看来短期内是用不了\u003C\u002Fp>","2014-02-04 07:59:01",{"id":107,"content":108,"user_id":39,"nick":40,"link":24,"date":109,"rid":103,"vote_up":19,"vote_down":19,"visible":35},89,"\u003Cp>新技术总是要人实践的嘛，起码在node中自己开启是完全可行的。预计ES6也将先在node中广泛应用起来。\u003C\u002Fp>","2014-02-05 05:29:06",{"id":111,"content":112,"user_id":113,"nick":114,"link":24,"date":115,"rid":19,"vote_up":19,"vote_down":19,"visible":35},90,"\u003Cp>实在是没有看明白“被动异步场景下的生成器使用”。其中的 myLogic 的意义似乎不大，不能体现出其作用。\u003C\u002Fp>\u003Cp>var p = $.ajax('\u002Findex.html');\u003Cbr>p.done(function(resp) { alert(resp); }); 不就是你要表达的意思么？\u003C\u002Fp>",48,"leavingme","2014-04-09 06:47:11",{"id":117,"content":118,"user_id":39,"nick":40,"link":24,"date":119,"rid":111,"vote_up":19,"vote_down":19,"visible":35},91,"\u003Cp>哥，你不要把我的三段代码合起来啊，好不容易才拆开的。重点是第三段代码除了调用next()外什么也没做，如果有很多个类似的异步操作，我们可以把第三段代码复用起来，通过封装到单独的模块或者框架中，让它不停地调用next()就行了。然后你再回头看前两段代码，还有回调么？尤其第二段，主要的逻辑流程，看起来是不是和同步代码差不多了？\u003C\u002Fp>","2014-04-09 14:14:00",{"id":121,"content":122,"user_id":113,"nick":114,"link":24,"date":123,"rid":117,"vote_up":19,"vote_down":19,"visible":35},92,"\u003Cp>似乎能够理解那么一点点了。\u003C\u002Fp>","2014-04-10 01:59:55"]