【ES6】异步操作和async函数

简介: 【ES6】异步操作和async函数
引言: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教学文章:


1. 【ES6】let与const 详解

2. 【ES6】变量的解构赋值

3. 【ES6】字符串的拓展

4. 【ES6】正则表达式的拓展

5. 【ES6】数值的拓展

6. 【ES6】数组的拓展

7. 【ES6】函数的拓展

8. 【ES6】对象的拓展

9. 【ES6】JS第7种数据类型:Symbol

10. 【ES6】Proxy对象

11. 【ES6】JS的Set和Map数据结构

12. 【ES6】Generator函数详解

13. 【ES6】Promise对象详解

14. 【ES6】异步操作和async函数

15. 【ES6】JS类的用法class

16. 【ES6】Module模块详解

17. 【ES6】ES6编程规范 编程风格

参考文献


阮一峰 《ES6标准入门(第2版)》

相关文章
|
5月前
|
前端开发 安全
协程问题之协程函数返回的Promise对象必须满足哪些要求
协程问题之协程函数返回的Promise对象必须满足哪些要求
|
7月前
|
前端开发
Promise链式调用与错误处理
Promise链式调用是处理异步操作的方式,它按顺序执行多个任务,每个任务返回Promise对象。通过`.then()`指定成功后的操作,`.catch()`处理错误。示例代码展示了如何使用fetch获取数据,根据状态码解析响应并处理数据,错误则通过`.catch()`捕获。另一个例子定义了三个异步函数构成Promise链,依次执行并处理结果,错误同样由`.catch()`统一管理。
|
前端开发
前端学习笔记202306学习笔记第四十二天-async函数的返回值2
前端学习笔记202306学习笔记第四十二天-async函数的返回值2
63 0
|
7月前
|
前端开发
Promise的链式调用
Promise的链式调用是指在一个Promise对象上连续调用多个then方法的过程。通过链式调用,可以将多个异步操作按照顺序执行,并且可以在每个操作完成后处理返回的结果。
70 0
|
前端开发 程序员
promise的链式调用和promise的嵌套的实现
promise的链式调用和promise的嵌套的实现
204 0
|
前端开发
前端学习笔记202306学习笔记第四十二天-async函数的返回值1
前端学习笔记202306学习笔记第四十二天-async函数的返回值1
55 0
|
C#
C#中await和async关键字的简单理解
C#中await和async关键字的简单理解
92 0
|
前端开发
async函数
async函数
79 0
|
前端开发 JavaScript
async函数 ------ ES6
async函数 ------ ES6
82 0
|
前端开发 JavaScript