async函数
定义
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函数的改进,具体体现在四点
1.内置执行器:
相对于Generator函数需要co模块或者next()方法来作为执行器执行,async函数自带执行器,所以上边的代码只需要一句
_readFile()
可以执行。
2.更好的语义:
async和await分别表示异步和等待,比起*和yield更容易理解。
3.更广泛的适用性:
yield后边只能跟Thunk函数或者Promise对象,但是在async函数中,可以跟Promise对象和基本数据类型。
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()
语法
返回一个Promise对象
说过很多次了,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)
)
Promise的状态改变
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命令
前边说过,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函数内部。
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,这样,还是会依次输出。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。