传统的异步方法
在还没有ES6之前,异步编程方式如下:
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
现在,我们有了Generator函数。
异步
想象一下你去某大型商城吃饭,然后你和你的朋友想去里面的一家美食店吃饭,到了门口发现前面排了很多人,需要挂号排队,于是你们领了一张号,然后就去逛别的地方,在期间时不时的需要回去看下是否快到排到你们了。直到差不多的时候你们就回去,就这样可以吃上饭了。
这样的方式,排队等待,在等待的期间去干别的事前,这种方式就叫异步!而如果只是单纯的排队不干别的事前,直到终于排到了就开始吃饭(执行),这种方式就叫同步!
也可以理解成任务被分成两段,先执行了第一段(领号排队),然后转而执行其他任务(去干别的事情),等做好了准备,再回头执行第二段(差不多快好了,回去吃饭)
回调函数
是JS语言对异步编程的实现。可以理解成把任务的第二段单独写在一个函数里面,等重新执行这个任务的时候,就直接调用这个函数,回调函数(callback),如下例子:
fs.readFile('/etc/passwd', 'utf-8', function(err, data){if(err) throw err;console.log(data)})
上面的代码中,readFile函数的第三个参数,就是回调函数 ,也就是任务二,等/etc/passwd这个文件返回后,回调函数才会执行。
需要注意的是,回调函数里面的第一个参数,必须是错误的对象err ,如果没有错误,这个参数就是null。
虽然可以,但不可滥用,如下:
fs.readFile('fileA', 'utf-8', function(err, data){fs.readFile('fileB', 'utf-8', function(err, data){fs.readFile('fileC', 'utf-8', function(err, data){// some code})})})
上面代码的意思是,需要读取文件A,才能执行读取文件B,读取了文件B才能执行读取文件C……
这种情况就叫做“回调函数地狱”,因为多个异步操作形成了强耦合,如果需要改其中一个回调代码,那可能别的回调也需要跟着改。为了避免这样的情况,就有了Promise对象!
Promise对象
Promise对象就是为了这样的情况而提出的,这不是新的语法功能,而是一种写法,可以把上面的嵌套改成链式调用。如下:
let readFile = require('fs-readfile-promise');readFile(fileA).then((data)=>{console.log(data.toString())}).then(()=>{return readFile(fileB)}).then((data)=>{console.log(data.toString())}).then(()=>{return readFile(fileC)}).then((data)=>{console.log(data.toString())}).catch((err)=>{console.log(err)})
使用then方法后,异步任务就看得更清楚了,但是问题是代码冗余,一堆的then,语义不太清楚。
这里,就由Generator函数来解决。
Generator函数
需要了解“协程”是什么,才能理解下面的话。具体看Generator函数
协程的运行步骤流程大致如下:
- 协程A开始执行。
- 协程A执行一半,进入暂停状态,执行权转移到协程B。
- 一段时间后,协程B交还执行权。
- 协程A恢复执行。
如下例子:
function* asyncJob(){// ...其他代码let f = yield readFile(fileA)// ...其他代码}
asyncJob函数是一个协程,奇妙在于yield命令,表示执行到这,就把执行权交给其他协程。由此看来,yield命令是异步两个阶段的分界线。Generator函数最大的优点,是可以在暂停的地方继续向后执行!
举一个例子,用Generator函数执行一个真实的异步任务:
let fetch = require('node-fetch');function* gen(){let url = 'https://api.github.com/users/github';let result = yield fetch(url)console.log(result.bio)}let g = gen();let result = g.next()result.value.then(function(data){ //因为模块node-fetch返回的是promise,所以可以用then的的return data.json();}).then(function(data){g.next(data);})
