Promise & Generator——幸福地用同步方法写异步JavaScript

最近在写一个自己的网站的时候(可以观摩一下~ Colors),在无意识中用 callback写了一段嵌套了5重回调函数的可怕的代码。回

最近在写一个自己的网站的时候(可以观摩一下~ Colors),在无意识中用 callback写了一段嵌套了5重回调函数的可怕的代码。回过神来的时候被自己吓了一跳,这可不行啊,丑得没法看啊!于是打算尝试一下一些流行的异步的解决方案。经过一番折腾之后...我终于找到了一个令自己满意的方案了(爱不释手)。不过在正式介绍它之前先扯一些其他的相关知识先吧!

1. JavaScript异步解决方案有哪一些

其实异步 JavaScript已经不是什么高级的东西了, Nodejs的出现,特别是 callback hell令人恐惧的写法已经成功倒逼出了很多很棒的解决方案。在这里看尤雨溪大神的这篇 小短文,非常精简扼要地介绍了当前常用的 async.js, Promise, co, async/await。个人建议有机会可以都试一下看看。而从个人的角度,我可能会以以下的标准来选择(个人喜好):

  1. 需要写爬虫之类控制并发数的我会用 async.js;它的有一些 API还是很方便的。

  2. 写前端的代码的时候可能会比较倾向于考虑 Promise,因为一般来说前端的异步场景除了 ajax之外貌似也不是很多了。而且之前使用过 isomorphic-fetch,感觉很棒。可以看我之前的文章~

  3. 后端代码 nodejs,那就非 co莫属了。根据尤雨溪大神的说法, es7async/await也只是 Promise & Generator的语法糖而已。而 co,就是结合了 PromiseGenerator的神一般的库。而本篇文章主要就是讲 co结合 PromiseGenerator的异步解决方法。

2. Promise & Generator简单入门

ES6是个好东西,其中的 PromiseGenerator可以说是精华的部分之一了。下面简单介绍入门一下 Promise以及 Generator。这一小节的介绍会很简单,而且也只是这两个新特性的一部分,但是提到的点都是本篇文章所需要的。当然,从学习的角度,应该找书去完全了解一下这两个特性,起码有个印象吧~个人感觉 ES6的学习可以去读 NCZUnderstanding ECMAScript6或者阮一峰大神的 ES6标准入门,都有电子书,很棒!前者语言比较浅显易懂,生动有趣,后者会更加详细,有条理一些。如果您已经对这些特性了如指掌的话,那就不用看这一小节了~

2.1 Promise

Promise有很多版本,也有很多实现的库,但是这里主要是介绍 ES6标准的内容。如果阅读以下几条特性觉得不懂的话建议先看看上面两本书相应的章节。

  1. 关于 promise,首先要意识到它是一种对象。这种对象可以用 Promise构造函数来创建,也可以通过 Nodejs本身一些默认的返回来获取这种对象。

  2. promise对象有三种状态: PendingFulfilledRejected。分别对应着未开始的状态,成功的状态,以及失败的状态。

  3. 这种对象常常封装着异步的方法。在异步方法里面,通过 resolvereject来划定什么时候算是成功,什么时候算是错误,同时传参数给这两个函数。这些参数就是异步得到的结果或者错误。

  4. 异步有成功的时候,也有错误的时候。对象通过 thencatch方法来规定异步结束之后的操作(正确处理函数/错误处理函数)。而 thencatchPromise.prototype上的函数,因此“实例化”之后(其实并非真正的实例)可以直接使用。

  5. 这个 promise对象还有一个神奇的地方,就是可以级联。每一个 then里面返回一个 promise对象,就又像上面所提的那样,有异步就等待异步,然后选择出规定好的正确处理函数还是错误处理函数。

2.2 Generator

Generator函数是一个带星星函数,而且是一个可以暂停的函数。

  1. 函数的内部通过 yield来推进函数。通过定义 yield后面的值来决定返回的 value

  2. 函数返回一个遍历器,这个遍历器有一个 next方法,可以获取一个对象,这个对象就包含了 yield定义好的参数。

关于ES6的知识的其它特性就不谈了,对写同(yi)步代码的话掌握以上这些已经足够了。

3. Co

噔噔噔噔!神奇的 Co登场了!这是一个 tj大神写的库。使用方法很简单,在 Github上的 README也讲得很清楚了。主要就是两点:

  1. Co函数里面包裹一个 generator函数,在 generator函数里面可以 yield promise对象,从而达到异步的目的。在 Co的内部实现里面是通过递归调用 next函数,把每一个 promise的值返回出来,从而实现异步转“同步”的写法。

  2. Co函数返回一个 promise对象,可以调用 thencatch方法来对 Generator函数返回的结果进行传递。方便进行后续的成功处理或者错误处理。

4. 如何用同步的写法写异步的代码

下面展示一段异步处理的代码,可以看到,同步的写法写异步真的很爽...

function *foo(res, name, newPassword, oldPassword) { try { // yield一个promise对象,如果有错误就会被后面的catch捕捉到,成功就会返回user。 const user = yield new Promise(function(resolve, reject) { // 常见的数据库读取星系 User.get(name, function(err, user) { if(err) reject(err) resolve(user) }) }) if(user.password != oldPassword) { return res.send({errorMsg:"密码输入错误!"}) } // 看到这一个异步函数和上一个的异步在写法上是基本上“同步”的,没有了相互嵌套,很优雅~也更加方便了debug~ yield new Promise(function(resolve, reject) { User.update(name, newPassword, function(err) { if(err) reject(err) res.send({msg: "你成功更换密码了!"}) resolve() }) }) } catch(e) { console.log("Error:", e) return res.send({errorMsg:"Setting Fail!"}) }}// 使用的话就直接调用co包含对应的Generator函数即可。co(foo(res, name, newPassword, oldPassword))

5. 总结

适合使用场景的方法才是最好的方法。但是当你在写 Node的时候开始受到回掉地狱的困扰的时候,不妨尝试一下 Co?用同步写法写异步的感觉真的很不赖啊!

如果文中有某些地方有错误或者不妥当的地方,欢迎指出来,感激不尽!互相学习才能进步嘛~

未登录用户
全部评论0
到底啦