async函数其实就是以前说过的Generator的语法糖,用于实现异步操做。它是ES2017的新标准。数组
读取两个文件:并发
const fs = require('fs') const readFile = function(filename){ return new Promise(function(resolve,reject){ fs.readFile(filename,function(error,data)){ if(error) return reject(error) resolve(data) } }) } const gen = function* (){ const gen1 = yield readFile('../1.txt') const gen2 = yield readFile('../2.txt') console.log(gen1.toString()) console.log(gen2.toString()) }
若是使用async函数的话,会是这么写异步
const _readFile = async function(){ const r1 = await readFile('../3.txt') const r2 = await readFile('../4.txt') console.log(r1.toString()) console.log(r2.toString()) }
通常状况下,只是把Generator的*换成async,把yield换成await。
async函数是Generator函数的改进,具体体如今四点async
1.内置执行器:函数
相对于Generator函数须要co模块或者next()方法来做为执行器执行,async函数自带执行器,因此上边的代码只须要一句fetch
_readFile()
能够执行。ui
2.更好的语义:url
async和await分别表示异步和等待,比起*和yield更容易理解。spa
3.更普遍的适用性:
yield后边只能跟Thunk函数或者Promise对象,可是在async函数中,能够跟Promise对象和基本数据类型。code
4.返回值是Promise
相比于Generator函数返回一个Iterator还须要遍历,async直接返回一个Promise能够直接调用then方法和catch方法。
async函数返回一个Promise对象,能够使用then方法添加回调,而后使用await关键字后,会等到异步操做执行完在执行后边的语句。
async function getPriceByName(name){ const symbol = await getSymbod(name) const price = await getPrice(symbol) return price } getPriceByName('WUBA').then(function(result){ console.log(result) })
上边的例子,是一个经过股票的名称,得到股票的代码,再得到价钱。前边声明async关键字,表示内部有内部操做,调用函数会返回一个Promise对象。
再看下一个例子,是一个指定多少毫秒后返回一个值。
function TimeOut(time){ return new Promise(function(resolve)){ setTimeout(resolve,time) } } async asyncTimeOut = function(value,time){ const t1 = await TimeOut(time) console.log(t1) } asyncTimeOut('hello',1000) asyncTimeOut('world',2000)
async函数的多种形式
函数表达式 const fun1 = async function(){ .... } 函数声明 async function fun2(){ .... } 箭头函数 const fun3 = async () => { .... } 对象中的变量 let obj = { async fun4(){ .... } } obj.fun4() 类的写法 class Async { constructor(){} async fun5(){ .... } } const a1 = new Async() a1.fun5().then()
说过不少次了,async函数返回一个Promise对象。
函数return的值将会做为then方法的参数
async function show(){ return '123' } show().then((v) => console.log(v))
show方法返回的值会被做为then方法的参数而调用。
若是在async函数内部抛出错误,会被catch捕获,这个时候Promise对象变成reject状态。
async function show2(){ throw new Error('出错了') } show2().then( v => console.log(v), e => console.log(e) )
async函数返回的Promise对象,必须等到函数内部全部的await执行完才会发生状态的变化,也就是说,得等到全部await执行完,才会执行后续的then方法。
async function getText(url){ const response = await fetch(url) const text = await response.text() return text.match('../aa/[a-z]}')[1] //反正就是一个正则匹配 } const url = '....' getText(url).then(v => console.log(v))
这个例子说明,得等到两个awiat都执行完才会console返回的数据。
前边说过,await命令后边跟随一个Promise对象。若是不是,会被转成Promise对象。
async function show(){ return await '123' } show().then((v) => console.log(v))
若是await后边的Promise对象变成了reject状态,会被后边的catch()捕获。
async function fun1() { return await Promise.reject('出错了') } fun1().catch(e => console.log(e))
若是async函数内部有多个await,可是只要一个await返回的Promise对象变成了reject状态,则整个函数马上捕获异常。
若是想要前边的正常抛出异常而不影响后边的await语句执行,能够把前边的写进一个try/catch中去。
async function fun2(){ try{ await Promise.reject('出错了') }catch(e){ } await Promise.resolev('hello') } fun2().then(v => console.log(v))
因为await后面跟随的是Promise对象,因此对象可能会有两个状态,一个resolve一个reject。因此,最好把await代码放到try/catch语句中比较好。
async function fun3(){ try{ await asyncFun1() await asyncFun2() } catch(e){ console.log(e) } } // 还有另一种写法 async function fun4(){ await asyncFun1().catch(e => console.log(e)) await asyncFun2().catch(e => console.log(e)) }
仍是第一种方法更好一点。直接写进try/catch语句中。
最好让多个await后边的异步操做同时发生,若是不是不存在前后顺序的话。
let a1 = await get1() let a2 = await get2()
上边的写法,get1执行完以后才会执行get2,若是get1和get2没有直接的关联,那样会很浪费时间。
//同时触发 let [a1,a2] = await Promise.all([get1(),get2()])
若是await放到async函数以外,就会报错,只能放到async函数内部。
原理也很简单,就是把Generator函数和自动执行器包装在一个函数中。
async function fn(){ ..... } // 等价于 function fn(){ return spawn(function *(){ ..... }) }
其中spawn函数就是自动执行器。
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); }); }); }
实际开发中常常会遇到各类异步操做,这里有一个例子。一次读取一组url,而后按照读取的顺序返回结果。
function readUrls(urls) { const textPromise = urls.map(url => { fetch(url).then(response => response.text()) }) // 按照顺序读出 textPromise.reduce((chain,textPromise) => { return chain.then(() => textPromise) .then(text => console.log(text)); },Promise.resolve()) }
分析一下,上边的代码,用fetch同时读取一种url,每一个fetch操做都返回一个Promise对象,放入textPromise数组,而后reduce方法一次处理每一个Promise对象,而后用then链接起来,一次输出结果。
缺点:这种方法看起来不太好理解,不太直观,用async函数会更好一点。
async function readUrls(urls){ for(const url of urls){ const response = await fetch(url) console.log(response.text()) } }
能够看到,代码是大大简化了,可是会有一个新的问题,就是必须等到前边一个读完了,才会读取下一个数据。
function readUrls(urls){ const textPromise = urls.map(async url => { const response = await fetch(url) return response.text() }) } //按照顺序输出 for(const text of textPromise){ console.log(await text) }
上边的函数,虽然map方法的参数是async函数,但倒是并发执行的,由于内部是继发执行,不影响外部。在后边的循环中使用了await,这样,仍是会依次输出。