** 本文适合有一定JS经验的开发者阅读 **

从很久以前开始我对于web前端的开发就不报以好感,js可以算是web前端的技术代表。 js自问世以来就伴随着许多设计问题,比如错误的闭包设计,诡异的prototype。 许多的开发者在此付出了很多(没有必要的)精力才把js逐渐推进现代化。

然而ES6还仅仅只能算现代化的开端,其中还有一些在我看来的错误的设计。 其中有一处错误设计就是Generator函数中的yield函数和next函数。 然而这一错误设计却被巧妙利用了起来,并催生了co.js这种大热的函数库。

Generator的设计错误

Generator函数的设计本意应该是类似于Python的generator,能够使程序出现一种懒惰(Laziness)的感觉。 (Laziness是被Haskell,Clojure等现代函数式编程语言所推广的一项特性,能够在特定条件下改善性能,恕我不展开例子。) 然而在Generator的函数实现里,却出现了 Sending values to the generator 的神奇特性。 在链接里,可以看到,通过调用

g.next(2); 

可以使generator的行为改变,具体来看, 就是代码定义中的var value = yield null会在本质上被替换成yield null; var value = 2

我并非PL类的大神,但这在我来看是个错误设计,原因有两点:

  • Generator的行为变成了非决定性(non-deterministic)的, 虽然js本来也并非函数式语言,然而我认为非决定性对于阅读代码,调试代码都相当不利。
  • 隐式的传参,破坏了程序的语意结构。同样,这一点会造成对源码阅读的困难。 然而却正是语意的破坏,才使这个设计缺陷被co.js利用起来。

错误设计的巧用和co.js的原理

我不喜欢写长篇大论。co.js的所实现的东西用一句话来概括就是: 能用同步的语法来写异步语句:

co(function*() {
    let x = yield Promise.resolve("YO");
    console.log(x); // "YO"
});

此功能类似于ES7的await,能够有效降低异步程序书写的复杂度。 这功能看起来很厉害,实际很简单。

如何实现呢?其实很简单,我写了个程序实现了一个精简版的co.js,姑且叫myco

function myco(gen) {

  return new Promise(function(resolve, reject) {

    let g = gen();
    onResolve()

    function onResolve(res) {
      next(g.next(res))
    }

    function next(res) {
      if (res.done) resolve(res.value);
      let v = res.value
      if (typeof v.then === 'function') v.then(onResolve);
    }

  });
}

// Usage
myco(function*() {
  let x = yield Promise.resolve("MYCO");
  return x;
}).then(function(res) {
  console.log(res);
});

这个程序去除了容错以及对于this的处理,其余的原理应该和co.js是一致的。 其核心机制是在g.next()的时候,把promise的resolve结果传入作为参数。 利用上面提及的“破坏程序语意”的设计,让异步操作在代码上可以看起来有同步的效果。

为啥?

看到以上,可能你会有几个问题:

  • 我们做了什么?:其实很简单,利用一个设计错误,做出了一个精简版的co.js库。
  • 这有什么意义?:实现了一个await的子集,可以大大降低异步代码的bug率。
  • 我们做了创新?:并没有,同样的概念在Clojure里有成熟,强大的实现(promise, future)。

为什么在PL语言的发展中会出现这种诡异的现象呢? 语言设计有问题落后于时代,社区却拼命把这个语言实现现代化,甚至把缺陷也加以利用实现新功能。 难道是爱?

我想并不是,只是在浏览器大战终结之前,Javascript的地位难以动摇。 强大的市场需求可以对一个残破的技术造成巨大推力,可以看见将来js会有现代化的一天。

然而从这个小问题出发,有一些事实开发者是应该清楚的:JS社区虽然活跃庞大积极,其并不先进。 开发者一方面在JS的区间里要勇于尝试新框架,因为不可否认这些新鲜框架是JS的未来的stepstone。 另一方面在JS之外应该积极尝试新语言,因为毕竟开发者除了开发,还应该有点hack的精神,不能止步不前固步自封。