引言:ES6新增的Generato、Promise、async都与异步编程有关。这里我们讲述async函数和相关的异步操作。 |
异步编程对于JavaScript语言极为重要。JavaScript 只有一个线程,如果没有异步编程, 得卡死,基本没法用。
ES6诞生前,异步编程的方法大概有下面4种:
回调函数
事件监听
发布/订阅
Promise对象
ES6将JavaScript异步编程带人了一一个全新的阶段,ES7 中的async函数更是给出了异步编程。
一、基本概念
所谓“异步”,简单说就是一个任务分成两段,先执行第一段, 然后转而执行其他任务,等做好准备再回过头执行第二段。
比如,有一个任务是读取文件进行处理,任务的第一段 是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫作异步。
相应地,连续的执行就叫作同步。由于是连续执行,不能插人其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
二、回调函数
JavaScript语言对异步编程的实现就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数中,等到重新执行该任务时直接调用这个函数。其英文名字“ callback"直译过来就是“重新调用”。
读取文件进行处理是这样写的。
fs.readFile( 'D://data.txt', function (err, data) { if (err) throw err; console.log(data); });
上面的代码中,readFiLe函数的第二个参数就是回调函数,也就是任务的第二段。 等到操作系统返回了文件以后,回调函数才会执行。
三、Promise
回调函数本身并没有问题,问题出在多个回调函数嵌套。假定读取A文件后再读取B文件,代码如下。
fs.readFile(fileA, function (err, data) fs.readFile(fileB, function (err, data) // ... }); });
不难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。这种情况就称为“回调函数噩梦”( callback hell )。
Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法, 允许将回调函数的横向加载改成纵向加载。采用Promise,连续读取多个文件的写法如下。
var readFile = require( 'fs-readfile-promise ); readFile(fileA) .then(function(data){ console.log(data.toString()); }) .then(function(){ return readFile(fileB); }) .then(function(data){ console.log(data.toString()); }) .catch(function(err) { console.log(err); });
上面的代码中使用了fs-readfile-promise模块,其作用是返回一个Pomise版本的readFile函数。Promise提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。
可以看到,Pomise 的写法只是回调函数的改进,使用then方法后,异步任务的两段执行看得更清楚了,除此以外并无新意。
Pomise的最大问题是代码冗余。原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。
四、async函数
这里我提供一个我经常使用的根据函数:时间延迟函数,在test函数中,可以间隔1秒在控制台打印“world”字符。
function sleep(ms){//时间延迟函数 return new Promise(resolve =>setTimeout(resolve,ms)) } async function test() { console.log('Hello') await sleep(1000) console.log('world!') }
ES7提供了async函数,使得异步操作变得更加方便。async函数就是Generator函数的语法糖。
一比较就会发现,async两数就是将Generator丽数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对Generator函数的改进体现在以下4点。
1.内置执行器。Generator函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行与普通函数一模一样,只要一行。
2. async函数会自动执行,输出最后结果。完全不像Generator函数,需要调用next方法,或者用co模块,才能得到真正执行,从而得到最终结果。
3. 更好的语义。async和wait比起星号和yield,语义更清楚。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
4.更广的适用性。co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async图数的await命令后面可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
5.返回值是Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了。
进一步说,async函数完全可以看作由多个异步操作包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
查看更多ES6教学文章:
参考文献
阮一峰 《ES6标准入门(第2版)》