ES6---async函数
- 一、基本用法
- promise异步编程 + async函数
- async函数多种使用形式
- (1)函数声明
- (2)函数表达式
- (3)对象的方法
- (4)箭头函数
- 二、async函数返回值
- 三、Promise 对象的状态变化
- 四、await 命令
- 五、错误处理
- 六、async函数使用注意点
- 七、async 函数的实现原理
- 八、按顺序完成异步操作
一、基本用法
- async函数处理异步编程,类似状态机。
- 封装多个promise进行异步编程。
- async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await。
- async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
promise异步编程 + async函数
let fun=function (){
return new Promise(function (resolve,reject){
setTimeout(function (){
resolve("数据");
},2000);
});
};
let asy=async function (){
let res=await fun();
return res;
}
asy().then(function (res){
console.log(res);//数据
});let fun=function (){
return new Promise(function (resolve,reject){
setTimeout(function (){
resolve("数据");
},2000);
});
};
let fun1=function (){
return new Promise(function (resolve,reject){
setTimeout(function (){
resolve("成功");
},3000);
});
};
let asy=async function (){
let res1=await fun();
let res2=await fun1();
return {
res1,res2
};
}
asy().then(function (res){
console.log(res);//{res1: "数据", res2: "成功"}---5s后输出
});
async函数多种使用形式
(1)函数声明
async function foo(){};
(2)函数表达式
const foo=async function (){};
(3)对象的方法
let obj={async foo(){}}; obj.foo().then(..)
(4)箭头函数
const foo=async ()=>{};
二、async函数返回值
- async函数返回一个 Promise 对象。
- async函数内部return语句返回的值,会成为then方法回调函数的参数。
async function f(){ return 'hello world'; } f().then(v=>console.log(v))//hello world
- 函数f内部return命令返回的值,会被then方法回调函数接收到。
- async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
async function f(){ throw new Error("出错了"); } f().then( v=>console.log('resolve',v), e=>console.log('reject',e) )
三、Promise 对象的状态变化
- async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
四、await 命令
- 1. 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f(){
return await 123;
//等同于return 123;
}
f().then(v=>console.log(v))//123
- 2. 另一种情况是,await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象。
- 3. await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f(){
await Promise.reject("出错了");
}
f()
.then(v=>console.log(v))
.catch(e=>console.log(e))//出错了
- await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
- 4. 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f(){
await Promise.reject("出错了");
await Promise.resolve("hello world");
}
- 第二个await语句是不会执行的,因为第一个await语句状态变成了reject。
- 5. 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个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
五、错误处理
- 1. 如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
async function f(){
await new Promise(function (resolve,reject){
throw new Error("出错了");
});
}
f().then(v=>console.log(v))
.catch(e=>console.log(e))
- async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。
- 2. 防止出错的方法,也是将其放在try…catch代码块之中。
async function f(){
try{
await new Promise(function (resolve,reject){
throw new Error("出错了");
});
}
catch(e){
}
return await("hello world");
}
f().then(function (res){
console.log(res);//hello world
}).catch(function (err){
console.log(err);
})
六、async函数使用注意点
- 1. await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
- 2. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
- getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
//写法一
let [foo,bar]=await Promise.all([getFoo(),getBar()]);
//写法二
let fooPromise=getFoo();
let barPromise=getBar();
let foo=await fooPromise;
let bar=await barPromise;
- 上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。
- 3. await命令只能用在async函数之中,如果用在普通函数,就会报错。
七、async 函数的实现原理
- async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){}
//等价于
function fn(args){
return spawn(function* (){
});
}
- 所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。
- 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,然后按照读取的顺序输出结果。
- Promise 的写法
function logInOrder(urls){
//远程读取所有URL
const textPromises=urls.map(url=>{
return fetch(url).then(response=>response.text());
});
//按次序输出
textPromises.reduce((chain,textPromises)=>{
return chain.then(()=>textPromises)
.then(text=>console.log(text));
},Promise.resolve());
}
- 使用fetch方法,同时远程读取一组 URL。每个fetch操作都返回一个 Promise 对象,放入textPromises数组。然后,reduce方法依次处理每个 Promise 对象,然后使用then,将所有 Promise 对象连起来,因此就可以依次输出结果。
- async 函数实现
async function logInOrder(urls){
for(const url of urls){
const response=await fetch(url);
console.log(await response.text());
}
}
- 所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。
- 并发发出远程请求
async function logInOrder(urls){
//并发读取远程URL
const textPromises=urls.map(async url=>{
const response=await fetch(url);
return response.text();
});
//按次序输出
for(const textPromise of textPromises){
console.log(await textPromise);
}
}
- 上面代码中,虽然map方法的参数是async函数,但它是并发执行的,因为只有async函数内部是继发执行,外部不受影响。后面的for…of循环内部使用了await,因此实现了按顺序输出。