异步编程:javascript是单线程,如果没有异步编程,会出现卡死的状态。 ES6诞生以前,异步编程的方法,大概啊有以下几种:
- 回调函数
- 事件监听
- 发布/订阅
- promise对象
- ES6将javascript异步编程带入了一个全新的阶段,ES7的Async函数更是提出了异步编程的终极解决方案
异步的概念:所谓“异步”,就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
比如:有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
回调函数:javascript语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。英文名叫callback,直译为“重新调用”。
在c盘下建立了一个文件夹a

写入一个b.text文件

b.txt

打开nodejs,输入:
fs.readFile('/a/b.txt',function(err,data) { if(err) throw err; console.log(data);});

可以看到:输出了一些二进制(计算机以二进制的方式进行存储)
其中回调函数(任务的第二阶段):
function(err,data) { if(err) throw err; console.log(data);}
等到操作系统操作返回了/a/b.txt这个文件后,回调函数才会执行。
nodejs约定:
1.回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。
缺陷: 回调函数本身并没有问题,它的问题出现在对各回调函数嵌套。假定读取文件A之后,再读取文件B,代码如下
fs.readFile('/a/b.txt',function(err,data) { fs.readFile('/a/c.txt',function(err,data) { if(err) throw err; console.log(data);});if(err) throw err; console.log(data);});
这里的c.text为空

这样一次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就成为“回调函数噩梦”,代码难以读懂和后期维护。
promise就是为了解决这个问题而提出的。它并不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改为纵向加载。采用Promise,连续读取多个文件,代码如下:
var readFile = require('fs-readfile-promise');
readFile('./a/b.text').then(function(data){
console.log(data.toString());
}).then(function(){
readFile('./a/c.text').then(function(data){
console.log(data.toString());
})
});
})
下面代码并不能正确运行,因为无法找到fs-readfile-promise这个机制(应该还需要进一步处理,才能使用这个,可以先忽略),但是可以从代码中,观察到:使用fs-readfile-promise模块的作用:就是返回一个Promise版本的readFile函数。Promise提供then方法记载回调函数,catch方法捕捉执行过程中抛出的错误。
但Promise的写法知识回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
缺陷:Promise的最大问题就是代码冗余,原来的任务被Promise包装了一下,不管什么操作,一眼看过去就是一堆then,原来的语义变得很不清楚。
更好的写法:
Generator函数:
协程:多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程。运行流程如下:
- 1. 协程A开始执行。
- 2.协程A执行到一半,进入暂停,执行权转移到协程B
- 3.(一段时间后)协程B交还执行权。
- 4.协程A恢复执行
上面的协程A就是异步任务,它分为了两段(或多段)执行
function *asnycJob() {
//..其他代码
var f = yield readFile(fileA);
//...其他代码
}
上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程,也就是说,yield命令是一部两个阶段的分界线。
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。
最大的有点:代码的写法非常像同步操作,去掉yield命令,简直一模一样。
Gernerator函数是协程在ES6的实现,最大的特点:可以交出函数的执行权(即暂停执行)。
整个Generator函数就是一个封装的异步任务,或者是异步任务的容器。异步操作需要暂停的地方,都用yields语句注明。
Gerrator函数的执行方法如下:
function *gen(x){
var y = yield x+2
return y;
}
var g = gen(1);
g.next();{value:3,done:false}
g.next();{value:undefined,done:true}
上面代码中,调用Generator函数,会返回一个内部指针(即遍历器)g。这是Generator函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象,调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,指行到x+2为止。
next方法的作用是分阶段执行Gerator函数,返回一个对象,表示当前阶段的信息:
value:yield语句后面表达式的值,表示当前阶段的值
done:布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段
Generator函数内部部署错误处理代码,捕获函数体外抛出错误
function * gen(x){
try {
var y =yield x +2;
}catch(e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了');
上面代码:
Generator函数体外,使用指针对象的throw方法抛出的错误,可以被函数以内的try...catch代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程是很重要的。
Thunk函数:
co模块:
async函数:
本文介绍了JavaScript异步编程,在ES6前有回调函数、事件监听等方法。ES6和ES7推动其发展。阐述了异步与同步概念,分析了回调函数、Promise的缺陷,还介绍了Generator函数、协程等更好的异步编程写法,如Generator函数可交出执行权,便于处理异步任务。
1212

被折叠的 条评论
为什么被折叠?



