传统的异步方法

在还没有ES6之前,异步编程方式如下:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

现在,我们有了Generator函数。

异步

想象一下你去某大型商城吃饭,然后你和你的朋友想去里面的一家美食店吃饭,到了门口发现前面排了很多人,需要挂号排队,于是你们领了一张号,然后就去逛别的地方,在期间时不时的需要回去看下是否快到排到你们了。直到差不多的时候你们就回去,就这样可以吃上饭了。

这样的方式,排队等待,在等待的期间去干别的事前,这种方式就叫异步!而如果只是单纯的排队不干别的事前,直到终于排到了就开始吃饭(执行),这种方式就叫同步!

也可以理解成任务被分成两段,先执行了第一段(领号排队),然后转而执行其他任务(去干别的事情),等做好了准备,再回头执行第二段(差不多快好了,回去吃饭)

回调函数

是JS语言对异步编程的实现。可以理解成把任务的第二段单独写在一个函数里面,等重新执行这个任务的时候,就直接调用这个函数,回调函数(callback),如下例子:

  1. fs.readFile('/etc/passwd', 'utf-8', function(err, data){
  2. if(err) throw err;
  3. console.log(data)
  4. })

上面的代码中,readFile函数的第三个参数,就是回调函数 ,也就是任务二,等/etc/passwd这个文件返回后,回调函数才会执行。

需要注意的是,回调函数里面的第一个参数,必须是错误的对象err ,如果没有错误,这个参数就是null

虽然可以,但不可滥用,如下:

  1. fs.readFile('fileA', 'utf-8', function(err, data){
  2. fs.readFile('fileB', 'utf-8', function(err, data){
  3. fs.readFile('fileC', 'utf-8', function(err, data){
  4. // some code
  5. })
  6. })
  7. })

上面代码的意思是,需要读取文件A,才能执行读取文件B,读取了文件B才能执行读取文件C……

这种情况就叫做“回调函数地狱”,因为多个异步操作形成了强耦合,如果需要改其中一个回调代码,那可能别的回调也需要跟着改。为了避免这样的情况,就有了Promise对象!

Promise对象

Promise对象就是为了这样的情况而提出的,这不是新的语法功能,而是一种写法,可以把上面的嵌套改成链式调用。如下:

  1. let readFile = require('fs-readfile-promise');
  2. readFile(fileA)
  3. .then((data)=>{
  4. console.log(data.toString())
  5. })
  6. .then(()=>{
  7. return readFile(fileB)
  8. }).then((data)=>{
  9. console.log(data.toString())
  10. })
  11. .then(()=>{
  12. return readFile(fileC)
  13. }).then((data)=>{
  14. console.log(data.toString())
  15. })
  16. .catch((err)=>{
  17. console.log(err)
  18. })

使用then方法后,异步任务就看得更清楚了,但是问题是代码冗余,一堆的then,语义不太清楚。

这里,就由Generator函数来解决。

Generator函数

需要了解“协程”是什么,才能理解下面的话。具体看Generator函数

协程的运行步骤流程大致如下:

  1. 协程A开始执行。
  2. 协程A执行一半,进入暂停状态,执行权转移到协程B。
  3. 一段时间后,协程B交还执行权。
  4. 协程A恢复执行。

如下例子:

  1. function* asyncJob(){
  2. // ...其他代码
  3. let f = yield readFile(fileA)
  4. // ...其他代码
  5. }

asyncJob函数是一个协程,奇妙在于yield命令,表示执行到这,就把执行权交给其他协程。由此看来,yield命令是异步两个阶段的分界线。Generator函数最大的优点,是可以在暂停的地方继续向后执行!

举一个例子,用Generator函数执行一个真实的异步任务:

  1. let fetch = require('node-fetch');
  2. function* gen(){
  3. let url = 'https://api.github.com/users/github';
  4. let result = yield fetch(url)
  5. console.log(result.bio)
  6. }
  7. let g = gen();
  8. let result = g.next()
  9. result.value.then(function(data){ //因为模块node-fetch返回的是promise,所以可以用then的的
  10. return data.json();
  11. }).then(function(data){
  12. g.next(data);
  13. })