NodeJS: 使用 (Generator) 生成器解决 JavaScript 回调嵌套问题

来源:互联网 发布:python url编码 编辑:程序博客网 时间:2024/06/08 15:57

转自:http://huangj.in/765

回调地狱 (http://callbackhell.com/) 作为 JavaScript 开发的门槛,一直让小白心力交瘁,也一直让大神以此鄙视小白。

为了解决这个问题,Async、事件触发机制、Promise/A 各种工作流的控制方案被提出并应用于各种场景下。

本文所介绍的 Generator 是 EcmaScript Harmony (ES6) 引入的新特性,并且通过该特性,我们能够很好地改变代码结构,以更可读的代码解决工作流中的回调嵌套问题。

实践出真知,本文的内容是基于 Node.js 0.11.9 环境下通过运行测试得出的结论。不保证与 ES6 标准完全一致。

如何创建 Generator

function * foo( input ) {  var res = yield input;}

使用上面的代码,我们就声明了一个 Generator 。

Generator 的使用

function * foo( input ) {  var res = yield input;}var g = foo(10);g.next(); // { value: 10, done: false }g.next(); // { value: undefined, done: true }

在上面的代码中,我们像使用一般函数一样,通过函数调用的形式得到了一个 Generatorg

Object.prototype.toString.call(g) // [object Generator]

我们可以使用 g.next() 方法使代码执行到下一个 yield 的位置。

如此反复,我们就可以将整个函数执行完。

g.next() 的参数和返回值

首先说返回值。

g.next() 的返回值包含两个属性,value  done

返回值的 done 属性

使用 done 属性,我们可以来检测生成器函数是否执行完成。例如上面的例子,当函数尚未结束时,done 为假;当函数全部执行结束后,done 为真。

这样在循环执行 next() 时,可以利用这个值的真假来决定是否继续循环。

while(!g.next().done) {  // do something;}

返回值的 value 属性

value 属性的值为 yield 关键字右侧表达式的值。

在上面的例子里,就是 input 的值。由于是表达式,我们可以在里面做各种运算。

function * foo( input ) {  var res = yield input / 2; // 修改这里的表达式}var g = foo(10);g.next(); // { value: 5, done: false } 5 == input / 2g.next(); // { value: undefined, done: true }

如上,我们修改改 yield 右侧的表达式,此时,调用 g.next() 时,我们得到了表达式运算后的结果。

g.next() 的参数

g.next() 的参数就是 yield 的返回值。举例说明

function * foo( input ) {  var res = yield input;  console.log('res is ', res);}var g = foo(10);g.next(); // 到达第一个 yield,返回 input 的值g.next(100); // 将 100 作为参数传入// console log// res is 100// 100 作为 yield 的返回值赋值给了 res 变量

用 Generator 改变回调的写法

了解了 Generator 的基本用法,这里就将其应用于具体的业务场景,我们看看他怎么解决回调嵌套的问题。

首先引入一个简单的工具来处理

// 当前的 Generatorvar activeGenerator;// 处理 g.next() 功能function gNext() {  return function (err, data) {    if (err) {      throw err;    }    // 前文中的 g.next(),并把回调函数的结果作为参数传递给 yield    activeGenerator.next(data)  }}// 控制工具function gQueue(generatorFunc) {  activeGenerator = generatorFunc(gNext());  activeGenerator.next();}

这个工具做的事情很简单,把生成器作为参数传递进去,他会自动地去触发 g.next() 来执行整个工作流。

一段工作流

function asyncFunc(cb) {  // 这个函数模拟一个异步操作,将在 1 秒后触发回调函数  setTimeout(function() {    cb(null, 100);  }, 1000)}// 声明一个 Generator 并传给 gQueuegQueue(function * flow(next) {  console.log('start');  // 执行异步函数 asyncFunc,并把 next 注册在其回调函数里  var y = yield asyncFunc(next);  // 回调执行完成后,会触发 g.next(),此时 y 的值为 asyncFunc 回调里的 100  console.log('y is', y);  // 同上  var z = yield asyncFunc(next);  console.log('z is ', z);  console.log('end')});// console log// start// y is 100// z is  100// end

看出来这段代码的作用了么。

asyncFunc 是个异步的函数,他会在 1s 之后触发回调。

我们的 gQuery 调用 Generator 的时候,会把一个 next 方法作为参数传入 Generator。

Generator 里异步方法调用时,将 next 注册在成功回调之后执行,此时,该方法会去主动调用 Generator 的 next 方法,并将异步方法回调的结果作为参数传递个 g.next()

就这样那样,你自己理解一下。然后代码就走完了。

看起来和 express/connect 的 next 参数差不多,但是仔细看,回调嵌套没有了。

原本需要回调嵌套的地方居然以类似同步代码的形式写出来了。

在 Generator 的帮助下,业务逻辑用同步形式的代码写出来了,并且后端还是以异步无阻塞的方式去执行。

是不是爽翻了!

总结

本文仅仅从功能上测试了使用 Generator 来回避回调地狱的问题,并给出了一种简单的基础方法,目前已经有相关框架( koa )和库( suspend )提供了使用 Generator 机制的控制流。

目前在 Node.js 0.11.9 下,可以在启动时添加 --harmony 参数来开启对 ES Harmony 的支持。

期待 ES Harmony 标准早日确定,也期待 Node.js 0.12 早日发布以把这些特性应用于生产环境中。


0 0