随着es6的发布和Babel的出现,对于异步编程,我们慢慢告别了之前回调地狱的问题,现在的我们有了更多的选择,例如:Promise, generator, async/await等方案。下面我们一一来了解一下:
###Promise
Promise在ES6发布之前就已经有很多的库和工具实现了这个功能,例如Jquery,p等等, Promise做到了将原来的回调地狱的方式,改为了链式返回,在一定程度上改善了异步编程的问题。
1 |
|
###Generator
Generator是ES6中新增的语法,Generator可以做到在函数执行过程中停止和继续进行,从而使异步函数可以以同步编程的方式编写,在异步的地方停止,在异步函数执行完后继续执行Generator函数。
在调用generator函数时,并不会执行函数内容,而是会返回一个迭代器(iterator)对象, 在执行迭代器的next()函数(next()函数是iterator的属性)时可以逐步执行generator函数内容。
注:一个对象要想可以生成迭代器,该对象需要有“Symbol.iterator”属性
1 | function* gen(){ |
让我们运行 gen()
1 | var g = gen(); |
对于上面的结果,我们也可以参看babel对于Generator的实现,不过这并不是官方的方案,只是可以作为理解Generator的一个帮助
1 | function gen() { |
由此,我们也可以猜测调用Generator的时候,调用了gen.prototype[Symbol.iterator]
,该函数相当于是把我们的代码根据标签yield进行分割,存储在一个“列表”中,并返回了一个迭代器,其中含有next属性,虽随着next的调用,一步步地执行“列表”中的每一段函数。
当我们运行的时候会发现上面的console.log(result);
输出了undefined
,这是为什么呢,因为Generator只是负责代码的停止和执行,它并不会等待后面的异步结束,而且一步步的写next太麻烦了,能不能让Generator自己执行到结束呢?
当然可以!
但是只有Generator并不能做到这些,因为Generator不知道异步代码什么时候结束,但是Promise知道啊,所以我们还需要上面所说的Promise。
为了方便大家理解,我们一步步来,我们先实现Generator的自执行,在每次调用next()时,都会返回一个对象,包含yield
后面语句的执行的返回值和一个是否完成全部Generator函数的标记done
,我们可以不断调用next直到done === true
为止,就完成了Generator的自执行
1 | function* gen(){ |
接下来我们需要在上面的基础上引入Promise,现在我们的run
函数中一个我们自己写的next
函数,显而易见的,我们只需要稍微改动一下这个函数即可。为了方便,我们还写了一个readFile
函数模仿异步代码,它会返回一个Promise并在1000ms后resolve。
1 | var readFile = function(filename) { |
上面的run
函数有一个问题,现在我们默认yield
后面一定是一个返回Promise的函数,那很多时候不一定会是这样,所以我们还需要一个转换器,把一切的对象都转为Promise,这个我们这里不再实现,具体可以参看TJ大神写的co。
async/await
在ES6的基础上,ES2017提供了async函数,这将得异步操作变得更加方便。我们把我们上面的gen函数用async/await改写一下。其实就是去除*,加上async, 所有的yield改为await就好了
1 |
|
上面的代码会和上一节我们最后的代码结果一样,这是得益于async
自带有自执行和异步等待。我们就不用再写自己的自执行函数和异步代码等待的逻辑了。await命令后面,可以是Promise对象,也可以是其他原始类型(数值、字符串和布尔值,数组等),是不是很酷炫。
另外不同Generator
返回一个迭代器,async
函数返回一个Promise
对象
所以上面的例子我们还可以加上
1 | gen().then(function(){ |
async/await很酷,但是现在浏览器还是没有完全支持,Node端只有7.0以上才可以使用,所以还是要使用Babel。
结束:
随着ES6的发布,我们的异步代码方案也越来越趋于成熟了