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

转自: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 / 2
g.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 的基本用法,这里就将其应用于具体的业务场景,我们看看他怎么解决回调嵌套的问题。

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

// 当前的 Generator
var 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 并传给 gQueue
gQueue(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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值