最近在写一个自己的网站的时候(可以观摩一下~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
?用同步写法写异步的感觉真的很不赖啊!
如果文中有某些地方有错误或者不妥当的地方,欢迎指出来,感激不尽!互相学习才能进步嘛~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。