最近在写一个自己的网站的时候(可以观摩一下~Colors),在无意识中用callback写了一段嵌套了5重回调函数的可怕的代码。回过神来的时候被自己吓了一跳,这可不行啊,丑得没法看啊!于是打算尝试一下一些流行的异步的解决方案。经过一番折腾之后...我终于找到了一个令自己满意的方案了(爱不释手)。不过在正式介绍它之前先扯一些其他的相关知识先吧!
1. JavaScript异步解决方案有哪一些
其实异步JavaScript已经不是什么高级的东西了,Nodejs的出现,特别是callback hell令人恐惧的写法已经成功倒逼出了很多很棒的解决方案。在这里看尤雨溪大神的这篇小短文,非常精简扼要地介绍了当前常用的async.js, Promise, co, async/await。个人建议有机会可以都试一下看看。而从个人的角度,我可能会以以下的标准来选择(个人喜好):
需要写爬虫之类控制并发数的我会用async.js;它的有一些API还是很方便的。
写前端的代码的时候可能会比较倾向于考虑Promise,因为一般来说前端的异步场景除了ajax之外貌似也不是很多了。而且之前使用过isomorphic-fetch,感觉很棒。可以看我之前的文章~
后端代码nodejs,那就非co莫属了。根据尤雨溪大神的说法,es7的async/await也只是Promise & Generator的语法糖而已。而co,就是结合了Promise和Generator的神一般的库。而本篇文章主要就是讲co结合Promise和Generator的异步解决方法。
2. Promise & Generator简单入门
ES6是个好东西,其中的Promise和Generator可以说是精华的部分之一了。下面简单介绍入门一下Promise以及Generator。这一小节的介绍会很简单,而且也只是这两个新特性的一部分,但是提到的点都是本篇文章所需要的。当然,从学习的角度,应该找书去完全了解一下这两个特性,起码有个印象吧~个人感觉ES6的学习可以去读NCZ的Understanding ECMAScript6或者阮一峰大神的ES6标准入门,都有电子书,很棒!前者语言比较浅显易懂,生动有趣,后者会更加详细,有条理一些。如果您已经对这些特性了如指掌的话,那就不用看这一小节了~
2.1 Promise
Promise有很多版本,也有很多实现的库,但是这里主要是介绍ES6标准的内容。如果阅读以下几条特性觉得不懂的话建议先看看上面两本书相应的章节。
关于promise,首先要意识到它是一种对象。这种对象可以用Promise构造函数来创建,也可以通过Nodejs本身一些默认的返回来获取这种对象。
promise对象有三种状态:Pending,Fulfilled,Rejected。分别对应着未开始的状态,成功的状态,以及失败的状态。
这种对象常常封装着异步的方法。在异步方法里面,通过resolve和reject来划定什么时候算是成功,什么时候算是错误,同时传参数给这两个函数。这些参数就是异步得到的结果或者错误。
异步有成功的时候,也有错误的时候。对象通过then和catch方法来规定异步结束之后的操作(正确处理函数/错误处理函数)。而then和catch是Promise.prototype上的函数,因此“实例化”之后(其实并非真正的实例)可以直接使用。
这个promise对象还有一个神奇的地方,就是可以级联。每一个then里面返回一个promise对象,就又像上面所提的那样,有异步就等待异步,然后选择出规定好的正确处理函数还是错误处理函数。
2.2 Generator
Generator函数是一个带星星函数,而且是一个可以暂停的函数。
函数的内部通过yield来推进函数。通过定义yield后面的值来决定返回的value。
函数返回一个遍历器,这个遍历器有一个next方法,可以获取一个对象,这个对象就包含了yield定义好的参数。
关于ES6的知识的其它特性就不谈了,对写同(yi)步代码的话掌握以上这些已经足够了。
3. Co
噔噔噔噔!神奇的Co登场了!这是一个tj大神写的库。使用方法很简单,在Github上的README也讲得很清楚了。主要就是两点:
Co函数里面包裹一个generator函数,在generator函数里面可以yield promise对象,从而达到异步的目的。在Co的内部实现里面是通过递归调用next函数,把每一个promise的值返回出来,从而实现异步转“同步”的写法。
Co函数返回一个promise对象,可以调用then,catch方法来对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?用同步写法写异步的感觉真的很不赖啊!
如果文中有某些地方有错误或者不妥当的地方,欢迎指出来,感激不尽!互相学习才能进步嘛~