本系列属于阮一峰老师所著的ECMAScript 6 入门学习笔记javascript
ES2017中引入了async函数,使异步操做变得更加方便。async是Generator函数的语法糖。java
const gen = function* (){ const f1 = yield readFile('/etc/fstab') const f2 = yield readFile('/etc/shells') } // 写成async函数 const gen = async function (){ const f1 = await readFile('/etc/fstab') const f2 = await readFile('/etc/shells') }
比较可知,async
函数将Generator函数的*
替换成async
,将yield
替换成await
,仅此而已es6
async
函数对Generator函数的改进有如下四点:shell
(1)内置执行器promise
Generator函数执行须要依靠执行器,而async
函数自带执行器。也就是说async
函数的执行与普通函数同样并发
gen()
(2)更好的语义化异步
async
和await
比起*
和yield
,语义更加清楚。async
表示函数有异步操做,await
表示紧跟在后面的表达式须要等待结果async
(3)更广的适用性函数
co
模块约定,yield
命令后面只能是Thunk函数或者Promise对象,而async
函数的await
命令后面能够是Promise对象和原始类型的值(数值、字符串和布尔值,但这等同于同步操做)学习
(4)返回值是Promise
async
函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,咱们能够用then
指定下一步的操做
async
函数会返回一个Promise对象,能够使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操做完成,再接着执行函数体后面的语句。
function timeout(ms){ return new Promise(resolve =>{ setTimeout(resolve,ms) }) } // 因为async函数返回的是Promise函数,所以可改写为 async function timeout(ms){ await new Promise(resolve =>{ setTimeout(resolve,ms) }) } async function asyncPrint(value,ms){ await timeout(ms) console.log(value) } asyncPrint('hello world',50) // 在50毫秒以后输出hello world // 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 () => {}
// async函数内部return语句返回的值,会成为then方法回调函数的参数 async function f(){ return 'hello world' } f().then(v => console.log(v)) // 'hello world' // async函数内部抛出错误,会致使返回的Promise对象变成reject状态,抛出的错误对象被catch回调函数接收 async function f(){ throw new Error('出错了') } f().then(v => console.log(v),e => console.log(e)) // Error:出错了 // async函数返回的Promise对象,必须等内部全部的await命令后面的Promise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误
// 正常状况下,await命令后面是一个Promise对象。若是不是,会被转成一个当即resolve的Promise对象 async function f(){ return await 123 } f().then(v => console.log(v)) // await后面的Promise对象变为reject状态,则reject的参数会被catch方法的回调函数接收到 async function f(){ await Promise.reject('出错了') } f().then(v => console.log(v)).catch(e => console.log(e)) // 出错了 // 只要有一个await语句后面的Promise变为reject,那么整个async函数都会中断执行 async function f(){ await Promise.reject('出错了') await Promise.resolve('Hello world') // 不会执行 } // 若是但愿前一个异步操做失败,也不中断后面的异步操做,这时能够把第一个await放在try...catch结构里面,这样不管这个异步操做是否成功,第二个await都会执行 async function f(){ try{ await Promise.reject('出错了') }catch(e){ } return await Promise.resolve('hello world') } f().then(v => console.log(v)) // 'hello world' // 另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误 async function f(){ await Promise.reject('出错了').catch(e => console.log(e)) return await Promise.resolve('hello world') } f().then(v => console.log(v)) // 出错了 hello world // 若是有多个await命令,能够统一放在try...catch结构中
(1)作好错误处理,最好把await
命令放在try...catch
代码块中
(2)多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发
let foo = await getFoo() let bar = await getBar() // 这两个独立的异步操做互不影响,被写成继发关系,这样比较耗时,可让他们同时触发,缩短程序的执行时间 // 写法一 let [foo,bar] = await Promise.all([getFoo(),getBar()]) // 写法二 let fooPromise = getFoo() let barPromise = getBar() let foo = await fooPromise let bar = await barPromise
(3)await
只能用在async
函数中,若是用在普通函数,就会报错
async函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里
async function fn(args){ // ... } // 等同于 function fn(args){ return spawn(function* (){ // ... }) } // 其中spawn函数就是自动执行器
如下例子比较async函数、Promise、Generator函数
// 某个DOM元素,部署了一系列的动画,前一个动画结束,才开始后一个。若是其中有一个动画出错,就再也不继续执行了,返回上一个成功执行的动画的返回值 // 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 }) } // Promise写法已经比回调函数的写法大大改进,但操做自己的语义不太容易看出来 // 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 }) } // Generator函数须要一个spawn自动执行器,并且必须保证yield语句后面的表达式必须返回一个Promise // async函数的写法 async function chainAnimationsAsync(elem,animations){ let ret = null try{ for(let anim of animations){ ret = await anim(elem) } }catch(e){ //忽略错误,继续执行 } return ret } // async函数的实现最简洁,最符合语义,代码量少
// 一次远程读取一组URL,按照读取的顺序输出结果 // Promise的写法 function logInOrder(urls){ // 远程读取全部URL const textPromises = urls.map(url =>{ return fetch(url.then(response => response.text())) }) // 按次序输出 textPromises.reduce((chain,textPromise) => { return chain.then(() => textPromise).then(text => console.log(text)) },Promise.resolve()) } // async函数写法 async function logInOrder(urls){ for(const url of urls){ const response = await fetch(url) console.log(await response.text()) } } // 这样简化了写法,可是全部操做都是继发,效率差,咱们须要并发请求 async function logInOrder(urls){ // 并发读取远程URL const textPromises = urls.map(async url =>{ const response = await fetch(url) return reponse.text() }) // 按次序输出 for(const textPromise of textPromises){ console.log(await textPromise) } } // map方法的参数是async函数,但他是并发执行的,由于只有async函数内部是继发执行,外部不受影响。后面的for...of循环内部使用await,所以实现了按顺序输出