传统的异步方法
在还没有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);
})