async函数
async函数的返回值为 promise 对象,promise对象的结果由async函数执行的返回值决定。async函数能使得异步操作变得更加方便,简而言之就是 Generator 的语法糖。
定义async函数,特点是即便函数内部返回结果不是promise对象,调用函数其最后的返回结果依然是promise对象,代码如下:
如果返回的结果不是 Promise 对象的情况下:
<script>asyncfunctionfn(){ // 返回的结果是字符串// return '123'// // 返回的结果是undefined// return;// 返回的结果是抛出一个异常thrownew'error' } constresult=fn() console.log(result); </script>
如果返回的结果是 Promise 对象时,我们正常使用 then 方法即可,如下:
<script>asyncfunctionfn(){ returnnewPromise((resolve,reject)=>{ // resolve('成功的数据')reject('失败的数据') }) } constresult=fn() // 调用 then 方法result.then((value)=>{ console.log(value); },(reason)=>{ console.log(reason); // 打印失败的数据 }) </script>
await 表达式
通过上文的对 async 介绍,感觉其功能有点鸡肋,其实恰恰不是,而是 async 需要搭配 await 一起使用才能达到语法糖的效果。
await的特点:
await必须写在 async 函数中
await右侧的表达式一般为 promise 对象
await返回的是 promise 成功的值
await的 promise 失败了,就会抛出异常,需要通过 try...catch捕获处理
说白了:await就相当于 then 方法的第一个回调函数,只返回成功的值,失败的值需要 try...catch来捕获。
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
<script>constp=newPromise((resolve,reject)=>{ // resolve('用户数据')reject('用户加载数据失败了') }) asyncfunctionfn(){ // 为防止promise是失败的状态,加上try...catch进行异常捕获try { // await 返回的结果就是 promise 返回成功的值letresult=awaitpconsole.log(result); } catch (error) { console.log(error);//因为是失败的状态,所以打印:用户加载数据失败了 } } fn() </script>
总结:
(1)await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
(2)如果有多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
比如:await Promise.all([a(), b()]),这里简单提一下
(3)await命令只能用在async函数之中,如果用在普通函数,就会报错。
(4)(理解一下async的运行原理) async 函数可以保留运行堆栈,普通函数内部运行一个异步任务时,如果异步任务运行结束普通函数可能早就运行完了,异步任务的上下文环境已经消失了,如果异步任务报错,错误堆栈将不包括普通函数;而async函数内部的异步任务运行时,async函数是暂停执行的,所以一旦async函数内部的异步任务运行报错,错误堆栈将包括async函数。
async使用形式
// 函数声明asyncfunctionfoo() {} // 函数表达式constfoo=asyncfunction () {}; // 对象的方法letobj= { asyncfoo() {} }; obj.foo().then( ) // Class 的方法classStorage { constructor() { this.cachePromise=caches.open('avatars'); } asyncgetAvatar(name) { constcache=awaitthis.cachePromise; returncache.match(`/avatars/${name}.jpg`); } } conststorage=newStorage(); storage.getAvatar('jake').then(…); // 箭头函数constfoo=async () => {};
async读取文件
和之前讲解的 promise 读取文件内容 一样,我们也可以使用async进行文件的读取,代码如下:
// 1.引入 fs 模块constfs=require('fs') // 2.读取文件functionindex(){ returnnewPromise((resolve,reject)=>{ fs.readFile('./index.md',(err,data)=>{ // 如果失败if(err) reject(err) // 如果成功resolve(data) }) }) } functionindex1(){ returnnewPromise((resolve,reject)=>{ fs.readFile('./index1.md',(err,data)=>{ // 如果失败if(err) reject(err) // 如果成功resolve(data) }) }) } functionindex2(){ returnnewPromise((resolve,reject)=>{ fs.readFile('./index2.md',(err,data)=>{ // 如果失败if(err) reject(err) // 如果成功resolve(data) }) }) } // 3.声明一个 async 函数asyncfunctionfn(){ leti=awaitindex() leti1=awaitindex1() leti2=awaitindex2() console.log(i.toString()); console.log(i1.toString()); console.log(i2.toString()); } fn()
async发送AJAX请求
和之前讲解 promise发送ajax请求 一样,我们也可以使用async进行发送ajax请求,代码如下:
<script>// 发送 AJAX请求,返回的结果是 Promise 对象functionsendAjax(url){ returnnewPromise((resolve,reject)=>{ // 创建对象constx=newXMLHttpRequest() // 初始化x.open('GET',url) // 发送x.send() // 事件绑定x.onreadystatechange=function(){ if(x.readyState===4){ if(x.status>=200&&x.status<300){ // 如果响应成功resolve(x.response) // 如果响应失败reject(x.status) } } } }) } // promise then 方法测试// const result = sendAjax("https://ai.baidu.com/").then(value=>{// console.log(value);// },reason=>{})// async 与 await 测试asyncfunctionfn(){ // 发送 AJAX 请求letresult=awaitsendAjax("https://ai.baidu.com/") console.log(result); } fn() </script>
与生成器(Generator)相比
我们发现 async与await之间的关系 和 Generator与yield之间的关系十分类似,不熟悉Generator的朋友可以看一下我之前的文章:生成器讲解 ;一比较就发现: async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await。代码比较如下:
<script>// Generator 函数function*person() { console.log('hello world'); yield'第一分隔线'console.log('hello world 1'); yield'第二分隔线'console.log('hello world 2'); yield'第三分隔线' } letiterator=person() // console.log(iterator); 打印的就是一个迭代器对象,里面有一个 next() 方法,我们借助next方法让它运行iterator.next() iterator.next() iterator.next() // async函数constperson1=asyncfunction (){ console.log('hello world'); await'第一分隔线'console.log('hello world 1'); await'第二分隔线'console.log('hello world 2'); await'第三分隔线' } person1() </script>
async函数的实现原理就是将 Generator 函数和自动执行器包装在一个函数里。
<script>asyncfunctionfn(args) {} // 等同于functionfn(args) { // spawn函数就是自动执行器returnspawn(function* () {}); } </script>
我们可以分析一下 Generator 和 async 代码的书写特点和风格:
<script>// Generator 函数functionGenerator(a, b) { returnspawn(function*() { letr=null; try { for(letkofb) { r=yieldk(a); } } catch(e) { /* 忽略错误,继续执行 */ } returnr; }); } // async 函数asyncfunctionasync(a, b) { letr=null; try { for(letkofb) { r=awaitk(a); } } catch(e) { /* 忽略错误,继续执行 */ } returnr; } </script>
所以 async 函数的实现符合语义也很简洁,不用写Generator的自动执行器,改在语言底层提供,因此代码量少。
从上文代码我们可以总结以下几点:
(1)Generator函数执行需要借助执行器,而async函数自带执行器,即async不需要像生成器一样需要借助 next 方法才能执行,而是会自动执行。
(2)相比于生成器函数,我们可以看到 async 函数的语义更加清晰
(3)上面就说了,async函数可以接受Promise或者其他原始类型,而生成器函数yield命令后面只能是Promise对象或者Thunk函数。
(4)async函数返回值只能是Promise对象,而生成器函数返回值是 Iterator 对象