Async函数是Generator函数的语法糖
async函数本质上是对generator函数的封装,使其更加有语义化,更加简洁,调用更加方便,其主要优化是如下几点:javascript
-
内置执行器。即async函数不须要像generator函数同样每次都须要调用
.next()
方法去执行,它封装了内部的执行器能够帮咱们自动的执行.next()
。java -
更好的语义化。async...await...语法使代码阅读更加有语义化。es6
-
更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的promise
await命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,异步
但这时等同于同步操做)。async
-
返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象函数
方便多了。你能够用then方法指定下一步的操做。进一步说,async函数彻底能够看优化
做多个异步操做,包装成的一个 Promise 对象,而await命令就是内部then命令的动画
语法糖。this
// async与generator的写法对比 const hello = function (name) { console.log(`hello ${name}`);; }; const say = function* () { yield hello('xiaoming'); yield hello('aiMi'); }; let g = say(); g.next(); g.next(); // 踩了一个坑,generator函数必需要先调用而且赋值给一个变量,才能够next,先调用不会执行,但会返回一个指向内部状态的指针对象(遍历器对象(Iterator Object)) // say().next(); const asyncSay = async function () { await hello('xiaoming'); await hello('aiMi'); }; let result = asyncSay(); console.log(result); // Promise {<pending>}
Async函数基本使用
async函数支持多种声明方式
// 函数声明 async function foo() {} // 函数表达式 const foo = async function () {}; // 对象的方法 let obj = { async foo() {} }; obj.foo().then(() => {}); // Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } } const storage = new Storage(); storage.getAvatar('jake').then(() => {}); // 箭头函数 const foo = async () => {};
一个简单demo来看下async函数在宏任务与微任务之间的表现
async function asyncTest1() { console.log('2'); await console.log('3'); } async function asyncTest2() { await console.log('4'); console.log('5'); } async function asyncTest3() { await new Promise(function(resolve) { console.log('6'); resolve(); }).then(function() { console.log('7'); }); console.log('8'); } console.log('1'); setTimeout(function() { console.log('9'); new Promise(function(resolve) { console.log('10'); resolve(); }).then(function() { console.log('11'); }) asyncTest1(); }); asyncTest2(); asyncTest3(); new Promise(function(resolve) { console.log('12'); resolve(); }).then(function() { console.log('13'); }); // 打印顺序 1==>4==>6==>12==>5==>7==>13==>8==>9==>10==>2==>3==>11
借用大神的一张图来理解一下js的执行顺序,一般咱们任务分为同步与异步,通常同步任务进入主线程,异步任务进入event table
并完成指定的事情之后注册回调函数进入event queue
,当主线程的任务执行完毕之后会从event queue
去取异步任务进入主线程。(ps:我的理解这里其实会遵循js单线程的原则,执行的一直是主线程。)这时候任务又会进行细分,分为宏任务与微任务,宏任务与微任务分别存放在不一样的event queue
中,当主线程完成之后会先取微任务中的任务,当微任务中的任务执行完之后再执行宏任务队列中的任务,全都完成之后会继续循环次操做(事件轮询)。
结合上述描述分析上面代码,由于以前不太肯定await后边会被分配在什么任务中,执行之后分析发现await后的内容能够看作是一个promise对象中的resolve,因此是当即执行的,属于主线程,await之后的会被丢进微任务中。
那么咱们能够肯定首先执行主线程中的任务即1,4,6,12;这时候settimeout中的内容被丢进宏任务,5,7,8,13被丢进微任务,又由于8是在await的promise对象的then以后,await的做用是其后的代码执行完毕之后才执行下一行代码,因此8被丢进了更后一层的微任务,因此如今会先按顺序执行微任务5,7,13,而后执行完之后再执行8;当这次循环汇中的微任务执行完之后开始执行宏任务,将宏任务中的内容归入主线程,按照以前的方式先执行主线程,遇到异步将异步丢进event table
,因此按顺序执行时9,10,2,3,最后剩下promise的then在微任务中,最后执行11。
Async函数使用注意
-
若是await后面的异步操做出错,那么等同于async函数返回的 Promise 对象被reject。因此最好把await命令放在try...catch代码块中。
-
多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。
-
await命令只能用在async函数之中,若是用在普通函数,就会报错。
-
async 函数能够保留运行堆栈。
-
还有一点须要注意的是咱们经过上边那段demo代码能够发现若是你await后边跟的是一个没有
.then()
的promise对象其实仍是至关于同步,因此要想要获得await后的代码全都执行完毕的结果必定要将异步代码放到.then()
中并暴露出来,这样await后的代码才会在其异步全都执行完之后再执行。
Async函数的实现原理
async function fn(args) { // ... } // 等同于 function fn(args) { return spawn(function* () { // ... }); } function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
与其余异步写法对比
// Promise function chainAnimationsPromise(elem, animations) { // 变量ret用来保存上一个动画的返回值 let ret = null; // 新建一个空的Promise let p = Promise.resolve(); // 使用then方法,添加全部动画 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一个部署了错误捕捉机制的Promise return p.catch(function(e) { /* 忽略错误,继续执行 */ }).then(function() { return ret; }); } // Generator 函数 function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }); } // async 函数 async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }
经过对比咱们能够发现async函数写法简洁语义明确,能够极大地减小代码量,而且让代码的可读性提升。在阮大神的《ECMAScript 6 入门》中还介绍了异步遍历器,for await...of,异步generator等等,感兴趣能够再详细研究一下。