(图片来源于互联网)
回调地狱
相信每一个 JS 程序员都曾被或者正在被回调地狱所折磨,特别是写过 Nodejs 代码的程序员。
1 2 3 4 5 6 7 8 9 |
asyncFun1(function(err, a) { // do something with a in function 1 asyncFun2(function(err, b) { // do something with b in function 2 asyncFun3(function(err, c) { // do something with c in function 3 }); }); }); |
JS 的后续传递风格(回调)是这门语言的优点也是这门语言的缺点,优点之一是我们可以很轻易的写出异步执行的代码,而缺点也是由异步引起的,当太多的异步步骤需要一步一步执行,或者一个函数里有太多的异步操作,这时候就会产生大量嵌套的回调,使代码嵌套太深而难以阅读和维护,即所谓的回调地狱。
解决方案
随着 JS 这门语言的发展,出现了很多处理回调地狱的解决方案。
具名函数
如最基本的,使用具名函数并保持代码层级不要太深
1 2 3 4 5 6 7 8 9 10 11 12 |
function fun3(err, c) { // do something with a in function 3 } function fun2(err, b) { // do something with b in function 2 asyncFun3(fun3); } function fun1(err, a) { // do something with a in function 1 asyncFun2(fun2); } asyncFun1(fun1); |
Promise
进阶一级的使用 Promise 或者链式 Promise,但是还是需要不少的回调,虽然没有了嵌套
1 2 3 4 5 6 7 8 9 |
asyncFun1().then(function(a) { // do something with a in function 1 asyncFun2(); }).then(function(b) { // do something with b in function 2 asyncFun3(); }).then(function(c) { // do somethin with c in function 3 }); |
Anync
使用 async 等辅助库,代价是需要引入额外的库,而且代码上也不够直观
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async.series([ function(callback) { // do some stuff ... callback(null, 'one'); }, function(callback) { // do some more stuff ... callback(null, 'two'); } ], // optional callback function(err, results) { // results is now equal to ['one', 'two'] }); |
Generator
现在,ES6 来了,ES6 带来了新一代解决回调地狱的神器——Generator,如果你不知道 Generator 是什么,可以看我之前写的 ES6 Generator 介绍。
Generator 本意上应该是一种方便按照某种规则生成元素的迭代器,不过鉴于其特殊的语法和运行原理,可以通过某种神奇的方式写出同步化的异步代码,从而避免回调,使代码更易阅读。
前文介绍过生成器的运行原理和 yield、yield*、next 等的用法,那么怎么用生成器写出异步执行的同步代码呢?
CO
串行的“ 同步代码”
首先看看下面的代码
串行化执行异步函数
1 2 3 4 5 6 |
co(function*() { var a = yield asyncFun1(); var b = yield asyncFun2(a); var c = yield asyncFun3(b); // do somethin with c })(); |
我知道你有疑问,co 是个什么鬼?co 是一个基于 Generator(生成器)的异步流控制器,可以完美实现写出非阻塞的“ 同步代码” 的目的。不解释更多... 直接上 github,TJ 大神的作品。 源代码算上大把注释也才 200 多行,为了帮助你理解,根据 co 的思想,我写了个最简单最初级的 co
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function co(genFun) { // 通过调用生成器函数得到一个生成器 var gen = genFun(); return function(fn) { next(); function next(err, res) { if(err) return fn(err); // 将res传给next,作为上一个yield的返回值 var < |