es6怎么用node执行

前言

在之前开发App的时候,一个首页就涉及到好几个不同的模块,这样在数据的显示上最好都走同一个请求接口,但各个模块的接口又都是通过爬虫抓的,所以在服务端需要等待所有的数据都了之后统一输出。这就是会涉及到回调相关。今天我们就来看看这个相关的异步回调如何解决,本文由@风君子翻译分享。

正文从这开始~

当我开始写nodejs的时候,非常讨厌两件事情:1.所有流行的模板引擎,2.回调的扩散(回调地狱)。我愿意忍受回调,因为我理解基于事件的服务端是多么的强大。但是自从我看到Java支持生成器以后,我知道我等了很久的这一天终于到来了。

今天,他们在V8和油猴子里实现了这个规范,新时代到来了(海贼王要诞生了。。)

尽管在V8(node)的命令行需要加harmony参数才能使用生成器,而且在所有的浏览器里实现还需要一段时间(Firefox已经全部支持了)。我们仍然可以学习怎么样用生成器来写异步代码。我们应该尽早掌握这种方法。

你可以下载0.11版本以后的node,然后给node传递–harmony 或者–harmony-generators参数来启用node对生成器的支持。我相信在不久的将来,当es6成为最终的标准的时候,这两个参数就不再需要了。

所以,到底生成器是如何解决回调地狱的呢?生成器函数可以通过yield关键字暂停执行,返回一个值。下次调用的时候从上次暂停的地方继续执行。这就意味着我们可以暂停一个函数的执行,等这个函数需要等待另外一个函数的执行结果的时候,而不是传递一个回调函数给它。

用英语来解释一种编程语言的结构是不是不好玩?那就直接上代码吧。

为了确保读者能跟上我的脚步,可以阅读A Closer LXXwGBsook at Generators Without

Promises

基础知识

在我们深入了解异步之前,我们先看看原生的生成器函数。生成器是通过function* 关键字定义的

es6怎么用node执行

我不想过多的关注这段代码,而想聚焦于怎么使用这个异步结构:

es6怎么用node执行

如果我想记笔记,我会这样写:

  • yield后面可以跟任何的表达式,这对于在任何结构中暂停一个函数的执行非常有用,例如foo(yield x, yield y)或者循环

  • 可以像调用函数一样调用生成器。但是他只返回一个生成器对象。你需要调用next方法唤起生成器的继续执行。当需要给生成器传值的时候,调用send方法。gen.next()和gen.next()等价。gen.throw从生成器内部抛出一个异常

  • 生成器不好返回原生值,而是返回一个有两个属性的对象:value和done。这样就很容易判断一个生成器是否执行结束。而不是像老的api一样抛出停止迭代的异常

异步解决方案1:suspend

你也许会问上面的代码到底是怎么解决node的回调地狱的。其实,如果我们可以暂停一个函数的执行。我们就可以通过一些语法糖,让异步的回调看上去像同步的一样。

问题来了:什么是语法糖?

第一个解决方案是suspend库。他是我们能想到的最简单的方式了。只有16行代码,用这个库的代码像这样:

es6怎么用node执行

suspend函数把生成器转换成了一个一般的函数。它给生成传递一个resume函数,作为异步函数的回调。另外它会通过一个含有error和value两个值的数组唤醒生成器。

生成器和resume之间的关系很有意思,但是它也有弊端。首先,返回一个两个元素的数组很烦人,即使用解构(var [err, res] = yield foo(resume))。我更倾向于返回一个值,如果有错误就抛出异常。看上去这个库支持这个选项,但是我认为应该把它作为默认值。

另外,需要显示的传递resume参数有点尴尬,而且也不好组合,因为有时候我想等到上面的函数执行完毕,我仍然需要传递一个callback参数进去,就像以前的node一样。这将导致更多的混乱和错误处理。因为error需要继续传递而不是抛出异常,所以你需要在每个异步调用里都手动检查,然后继续传递error对象。

最后,你也没办法做一些像并行处理一些复杂的控制流这种事情。README里说其他的控制流库解决了这个问题。你需要结合suspend和这些库中的其中一个。但是我更想看到生成器原生支持这些库。

ps:kriskowal 提到了creationix写的gist.更好的实现了单独的生成器来处理基于回调的代码。它很酷,默认抛出error。而且更简洁

异步解决方案2:Promises

更好的处理异步的方案是promises.promise是一个代表了将来值的一个对象。你可以组合promise来代表异步调用的控制流行为。

我不打算在这里过多的介绍异步控制流,这需要费好多时间,另外已经有了很好的解释,最近有很多人强调要定义promise的API和行为,允许在不同的库之间交互,其实想法很简单。

我用的是Q,因为他已经支持生成器,而且比较成熟了。它的早起实现叫taskjs,但是不是标准的promise实现。

让我们看一个真实世界的例子。我们经常用一些简单愚蠢的例子。下面这段代码创建一个博客对象,然后返回它:

es6怎么用node执行

这甚至不是一个复杂的例子,你看看它有多丑。回调的很快把代码挤到了屏幕的右边。而且,为了查询所有的标签。我们需要手动管理每一个查询条件,确保他们已经准备好。

让我们用Q来重新实现:

es6怎么用node执行

我们把这些基于回调的代码改成了基于promise的。但是这个只是个简单的例子,只要我们有了promise,就可以调用then方法来等等异步操作的执行结果。更多的细节请参考promises/A+规范。

Q还实现了其他的一些方法,比如all:一个promise的数组作为参数,等待他们都执行完毕。当执行完的时候,也就意味着所有的异步流程都执行结束了,而且所有未被处理的错误都被抛出。根据promises/A+规范。所有的异常都会被转换为error,然后传递给错误处理函数。所以需要却被没被处理的错误被重新抛出(如果你不太理解,请阅读这篇文章)。

注意到我们需要嵌套最终的promise handler。因为我们需要访问post和taggedPosts,不幸的是,这看起来有点像callback。

是时候见识一下生成器的力量了:

es6怎么用node执行

这是不是很神奇?让我们看看到底发生了什么

Q.async传入一个生成器,返回一个调用生成器的函数,有点像suspend库。然而,有一个关键的区别:生成器yields promis//www.58yuanyou.come。Q把promise和每一个生成器绑定到一起,当promisefullfill的时候唤醒生成器,返回结果。

我们不需要处理笨拙的resume函数,promise隐式处理了。我们从promise中获益良多。

其中一个好处是我们可以在需要www.58yuanyou.com的时候使用Q,比如Q.all,Q.all可以并行的运行一系列异步操作,用这种方法,可以很容易的把显式的Q promise和隐式的promise结合在一起来创建看上去很简介的复杂的控制流。

另外,还没有嵌套的问题。因为post和taggedPosts在相同的作用域里,所以我们不需要关心then链会打破作用域。这太帅了。

错误处理有点棘手。在生成器里使用promise之前,你需要确实理解promise是怎么样工作的。在promise里错误和异常会被传递给错误处理函数,而不是被抛出。你可以通过错误回调来处理抛出的错误。someGenerator().then(null, function(err) { ... }).

另外,需要注意的是可以使用gen.throw方法抛出生成器内部的promise错误。这也就意味着可以用try/catch来处理生成器内部的错误

es6怎么用node执行

它会按照你期望的方式工作。db.hgetall产生的错误都会被catch捕获,即使是嵌套的Q.all产生的错误。当然了,如果没有try/catch的话,异常会被转换为错误,然后传递给调用promise的错误处理函数。

思考一下:我们可以用try/catch来给异步代码添加错误处理函数。错误处理的动态作用域也能正常工作。所有在try里发生的未被处理的异常都会原由网扔给catch。还可以用finally。

另外,当你调用promise的done方法的时候,默认会返回all方法的异步代码发生的错误。

es6怎么用node执行

上面的代码是标准库代码,创建promise但是不关心错误处理,你可以这样调用:

es6怎么用node执行

这是最上层的代码。就像前面说的,done方法确保抛出未被处理的异常。我相信上面这种模式很普遍,但是还有一种新的方法。getTaggedPosts是库方法,也就是一种promise-generating方法。

我提了一个Q.spawn的PR,现在已经merge到Q里面了,现在可以更简洁的写上面的代码

es6怎么用node执行

spawn需要一个生成器对象作为参数,然后立即调用它,自动抛出未处理的错误。它其实等价于Q.done(Q.async(function*() { ... })())

其他方法

我们的基于promise的生成器代码越来越屌了,用一些语法糖,我们可以甩掉很多异步流带来的包袱。研究了这么久,这里有一些模式需要注意:

什么时候用

如果只有一个简单的函数等待一个promise,那没必要用生成器,比较下面的代码

es6怎么用node执行

和这个代码:

es6怎么用node执行

我觉得上一个更简洁

spawnMap

我发现我自己做了很多事情:

es6怎么用node执行

用spawnMap还是很有帮助的,它帮你实现了Q.all(arr.map(Q.async(...))).

es6怎么用node执行

asyncCallback

我发现的最后一件事是有好几次我想创建一个Q.async函数,我需要确保所有的错误都会被重新抛出,但是我不能把上面的callback转成 Q.async,因为所有的错误多被静默处理了。而且我也不能用Q.spawn,因为它不是立即执行的。这种情况发生在来自于不同库的常规回调上,比如express的app.get('/url', function() { ... })

也许有时候确确实实需要异步回调:

es6怎么用node执行

最后的想法

当我发现生成器的时候,我希望它确实能够改善异步代码。事实证明确实可以,尽管你需要确实理解promise结合生成器是怎么工作的。把promise隐式话会把他们弄的有点神秘,所以在你理解promise之前,我不会用async和spawn。

尽管我希望js的魔法能过延续,所有的这些都会被js隐式处理,这种解决方案还是很赞。现在我们有了一种简洁的处理异步行为的方式,这种方式很有用,不仅仅用来处理文件操作。甚至可以写一些分布式的代码,比如跨CPU的或者跨机器的。

后语

当外面的世界在谈异步中调异步有多么痛苦的时候,但身处这样的环境一下子可能还感受不到//www.58yuanyou.com,但可以自己想个模块来做,生搬硬套把要用的技术用进去,那样之后可能就会所有的“学习”。

关于本文

译者:@风君子

译文:http://www.lixuejiang.me/2016/12/04/用ES6生成器解决node回调地狱/

原文:http://jlongster.com/A-Study-on-Solving-Callbacks-with-Java-Generators

每天早读,三万同行一起成长

投稿合作联系(微信号):zhgb_f2er

内容版权声明:除非注明原创否则皆为转载,再次转载请注明出处。

文章标题: es6怎么用node执行

文章地址: www.58yuanyou.com/jiqiao/225818.html

相关推荐