文章目录
一、概念
async函数:处理异步编程 类似状态机; 封装多个promise进行异步编程
也就是 Generator 函数的语法糖。
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
将 Generator 函数的星号(*)替换成async,将yield替换成await:
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
特点:
- async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结
- async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
- async函数的返回值是 Promise 对象
- async函数内部return语句返回的值,会成为then方法回调函数的参数
针对3、4:
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('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出错了
二、对比 Promise联用Generator && Promise联用async
1.Promise联用Generator
let fun= function () {
return new Promise(function (resolve,reject) {
setTimeout(function () {
resolve("数据");
},2000)
})
}
let f= function* () {
yield fun();
}
let inter=f();
//console.log(inter.next()); //{value: Promise, done: false}
inter.next().value.then(function (res) {
console.log(res); //两秒后输出: 数据
});
2.Promise联用async
async函数返回一个 Promise 对象
async函数内部return语句返回的值,会成为then方法回调函数的参数
let fun= function () {
return new Promise(function (resolve,reject) {
setTimeout(function () {
resolve("数据");
},2000)
})
}
//async函数 返回也是promise对象 then catch finally
let asy=async function () {
let result=await fun();
return result;
}
asy().then(function (res) {
console.log(res); //两秒后输出: 数据
})
3.async处理多个Promise(等待间无关)
let fun1= function () {
return new Promise(function (resolve,reject) {
setTimeout(function () {
resolve("数据");
},2000)
})
}
let fun2= function () {
return new Promise(function (resolve,reject) {
setTimeout(function () {
resolve("成功");
},3000)
})
}
let asy=async function () {
let res_1=await fun1();
let res_2=await fun2();
return{
res_1,res_2
}
}
asy().then(function (res) {
console.log(res);
})
总共等待五秒(两者之和)后输出:
4.async处理多个Promise(等待间有关)
let method=async function(){
let p1=await new Promise(function (resolve) {
resolve(true);
})
let result=await (function (args) {
return new Promise(function (resolve) {
if(args){
resolve("成功");
}
else{
resolve({result:'error'});
}
})
})(p1);
return result;
}
method().then(function (res) {
console.log(res); //成功
})
三、Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变(除非遇到return语句或者抛出错误)
即只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数(例如上面的第3个例子)
四、await 命令
1.正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。
2.如果不是 Promise 对象,就直接返回对应的值
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
3.实例不是 Promise 对象,但是因为定义了then方法,await会将其视为Promise处理
4.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,效果是一样的。
5.任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject
6.有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
(1)这时可以将第一个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
(2)另一种方法是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后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject
let fun=async function(){
await new Promise(function (resolve,reject) {
throw new Error('erroe');
})
return '123';
}
fun().then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
//输出: Error: erroe
1.使用try…catch来防止出错
let fun=async function(){
try{
await new Promise(function (resolve,reject) {
throw new Error('erroe');
})
}
catch(e){
// throw e; //不写可以防止抛出异常,正常返回值
}
return '123';
}
fun().then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
//输出: 123
如果要捕获异常:
let fun=async function(){
try{
await new Promise(function (resolve,reject) {
// throw new Error('error'); //两种都可以
reject("失败");
})
}
catch(e){
throw e; //不写可以放置抛出异常,正常返回值
}
return '123';
}
fun().then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
//输出: 失败 或 Error: error
2…如果有多个await命令,可以统一放在try…catch结构中
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
六、async 函数注意点
1.把await命令放在try…catch代码块中
前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
前面示范了两种写法
2.同时触发多个await (Promise.all)
多个await命令后面的异步操作,如果不存在继发关系,最好把它们整合成一个promise,让它们同时触发
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;
3.await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。
function dbFuc(db) { //这里不需要 async
let docs = [{}, {}, {}];
// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
上面代码可能不会正常工作,原因是这时三个db.post()操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}