一.含义
- ES7标准引入了async函数,
该函数专门用于处理异步操作,属于generator函数的语法糖
- 看个例子
// 1. generator函数
function * gene(){
yield 5;
yield 'hello world'
}
var res=gene()
console.log(res.next());//{value: 5, done: false}
console.log(res.next());//{value: "hello world", done: false}
console.log(res.next())//{value: undefined, done: true}
// 2. async函数
async function a(){
await console.log(5); //5
await console.log('hello world'); //hello world
}
// 返回的是一个promise对象(即使没有return)
console.log('async返回值',a()) //async返回值 Promise {<pending>}
直观区别
- generator函数和async函数最直观的区别就是generator的*可以被替换成async并且换个位置,yield语句可以被替换成await语句
generator函数需要通过next方法控制函数执行进度,而async函数不需要next等方法来进行控制
- 官方的说法就是async函数自带执行器,所以和普通函数一样,直接调用函数就可以了
其他区别
async返回的是一个promise对象,而generator函数返回的是一个iterator对象,该iterator对象需要调用next方法才能继续执行
- 官方说法是async函数比generator函数具有更好的普适性,
因为generator函数的yield语句后面只能是thunk函数和promise对象
,但是不理解
- 关于thunk函数请看generator函数的异步应用
- 而async函数后面可以跟promise对象和普通数据类型(普通数据类型会被转换为promise对象
状态默认为resolved
返回如果接的是普通函数,那么不会等待promise状态改变,也就是相当于没效果
)
二.基本用法
- async函数会返回一个promise对象,await语句后面可以接普通数据类型好promise对象
注意return返回的promise对象可能在await语句执行完毕之前就返回了
- 即使await语句卡住了,return也是可以返回promise对象!
但是返回值为await返回的默认值undefined!说明value没有改变
// 1. await语句后面接普通数据类型
async function a(){
// 1.1 普通数据类型有没有执行。此时看不出来
await true; // 执行了
await 1; // 执行了
await 'h'; // 执行了
// 1.2 使用()包裹运算语句
var x;
await (x=5+9); // 执行了
console.log('x',x) //x 14
// 1.3 执行普通函数
await console.log('await后面接函数');//await后面接函数
return '有返回值'
}
// promise对象的[[PromiseValue]]为 "有返回值"
console.log(a())
// 2. async函数返回的是promise对象,所以可以作为await语句后面的语句
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms); // 需要等待promise对象的状态变为resolved或者rejected才会继续执行
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
// 因为没有return 所以打印的promise对象没有值
console.log(asyncPrint('hello world', 1000));//Promise {<pending>}
// 3. await函数后面接promise对象(状态不改变)
async function q(){
await new Promise((res,rej)=>{
console.log("状态不改变")//状态不改变
//res();// 注释掉res/rej,那么该函数一直停在这里
}).then(()=>{console.log('res')},()=>{console.log('rej')})
// 注释掉res/rej之后,这里不会打印了
console.log('此处不会执行')
return '即使await停止,还是会return返回promise对象,但是value未undefined默认值'
}
console.log(q());//[[PromiseValue]]: undefined
async函数的使用形式
// 1. 函数声明(不能是匿名函数)
// async function (){};//Function statements require a function name
async function a(){}
// 2.函数表达式
var b=async function(){}
// 3.作为对象属性
var obj={async c(){}}
console.log(obj)//{c: ƒ}
// 4.类的方法
class d{
constructor(name){
this.name=name
}
async getName(){
await console.log("实例化类,执行async")
// 4.1. return基本数据类型
// return 'hello'
// 4.2. promise对象
// return new Promise((res,rej)=>{
// // 4.2.1 改变状态
// // res('4.2.1')
// // console.log('res')
// // 4.2.2 不改变
// console.log('4.2.2不改变')
// })
// 4.3 await
return await new Promise((res,rej)=>{
//4.3.1
// console.log("await改变promise状态")
// res('改变成功')
// 4.3.2 不改变
console.log('3.2')
})
}
}
var dd=new d('dog')
console.log(dd.getName())
/* 4.
1. [[PromiseValue]]: "hello"
2.1 [[PromiseValue]]: "4.2.1"
2.2 打印4.2.2不改变 得到[[PromiseValue]]:undefined
3.1 [[PromiseValue]]: "改变成功"
3.2 [[PromiseValue]]: undefined
*/
// 所以return返回promise对象其实和return await promise对象的作用是一样的!
三.语法
promise对象
- async函数会返回一个promise对象,所以我们可以对该对象添加then,catch等方法
最重要的是,即使在async内部没有捕获到错误,也可以通过给返回的promise对象添加catch/reject方法来捕获错误
// 1. async函数,用于捕获返回值
async function a(){
return 'hello world'
}
// 添加then方法(用于捕获返回值)
a().then((data)=>{
console.log(data);//hello world
})
// 2. async函数,用于捕获内部错误
async function b(){
throw new Error("err")
}
b().catch((err)=> {
console.log(err) // Error: err
})
promise对象状态变化
- 当await后面接的不是promise对象时,
如果是普通数据类型那么会直接执行,因为默认转换为promise对象且状态是resolved状态
但是当为函数时,如果存在setTimeout,那么也不会等待,因为没有状态改变,所以立即返回。但是该函数依旧会执行
如果是promise对象,那么必须等到状态改变为resolve/reject才能执行剩下的方法和return语句等,then/catch也无法执行
// 1. 必须等待async函数的await执行完毕,才会执行返回的promise对象的then
var n=1
async function a(){
// 1.1 await后面接的不是promise对象,那么不需等待状态改变
/* await setTimeout(function(){
console.log('此时执行')
n++;
},1000) */
/* 由于没有promise对象,不会等待状态改变,所以得到的n为1 */
/* 但是setTimeout的确执行了,1秒后全局获取n会得到2 */
// 1.2 await语句后面接的是promise对象,那么会等待状态改变
await new Promise((res,rej)=>{
setTimeout(function(){
console.log('此时执行')
n++;
res()
},1000)
})
/* 会在1秒后才return,所以得到的n:2 */
return 'n的值为:'
}
// n的值为: 1
a().then(data => console.log(data,n))
thenable对象
如果await命令后面跟的是一个thenable对象(具有then方法的对象),那么await会把该对象当成promise处理
// 1. 对象具有then方法,那么await会自动调用该方法
var obj={
then(res,rej){
var s=new Date().getTime()
setTimeout(function(){
res(new Date().getTime()-s)
},1000)
}
}; // 这个分号不能省。
// 注意,适用立即执行函数,上个语句一定要加分号。。。
(async () => {
var res=await obj
console.log(res);// 1001
})();
// 2.还可以给类添加then方法,那么实例化对象也是thenable对象
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
};
(async () => {
const sleepTime = await new Sleep(1000);
console.log(sleepTime); //1002
})();
根据async/await,可以实现整个程序的休眠
/* 注意,只是让promise休眠了,主线程并没有休眠。。
所以b().then()之后的下一行会立即执行!*/
var s=new Date().getTime()
async function b(){
var d=await new Promise((res)=>{
setTimeout(()=>{
res('ddd')
},1000)
})
console.log(d,new Date().getTime()-s)
}
b().then((res)=>{
console.log('1000毫秒后执行')
})
console.log(new Date().getTime()-s)
console.log(`休眠${new Date().getTime()-s}毫秒后才执行这里`)
错误处理
- then,catch可以接受reject,抛出的错误
// 1. then的第二个参数接受reject数据
async function f(){
await new Promise((res,rej)=> rej("err"))
}
f().then(res=> {}, rej => {console.log(rej)});//err
// 2. catch可以接受reject的数据
async function b(){
await new Promise((res,rej)=>{
rej('rej')
})
}
b().catch(err => console.log(err));// rej
// 3. then的第二个参数函数可以接收抛出的错误
async function c(){
throw new Error("err")
}
c().then(res => console.log(res),
rej => console.log(rej));//Error: err
// 4. catch可以接收抛出的错误
c().catch(err => console.log(err));//Error: err
- async函数的错误其实应该在内部捕获,使用try-catch语句
async function e(){
try{
await new Promise((res,rej)=>{
rej('err')
})
}catch(e){
console.log('错误:',e);//错误: err
}
}
e()
注意点
- 建议await语句都放在try-catch语句块中,因为await后面的promise对象可能抛出错误
多个await命令后面的异步操作,如果是独立的异步操作,最好同步触发(省时,一起触发了)
function a(time,num){
return new Promise((res)=>{
setTimeout(()=>{
res(num)
},time)
})
}
async function b(){
// 1. Promise.all([]) 同步触发多个异步操作
// var [x,y]= await Promise.all([a(1000,3),a(2000,5)])
// 2. 分开写
var x=a(1000,3)
var y=a(2000,5)
await x
await y
console.log(x,y);//3 5
}
b()
await语句必须用在async函数中,用在其他地方会报错
数组的高阶方法(有参数)使用async可能会得到错误的结果。
var arr=[1,2,3,4]
function getdata(num,i){
return new Promise((res,rej)=>{
setTimeout(function(){
res(num)
},1000*i)
})
}
// 1.forEach(并不是一个数执行完毕再到另一个数,而是差不多一起执行)
// arr.forEach(async function(item,i){
// console.log(await getdata(item,Math.abs(2-i)))
// })
// 此时得到的结果为 3 2 4 1,因为异步操作设置的等待时间不同,所以得到顺序不同!
// 所以不是我们想要的结果!
// 2. map (此时和原来的一致,因为是按照顺序返回结果的)
arr.map(async function(item,i){
return await getdata(item,Math.abs(2-i))
})
// console.log(arr);//[1, 2, 3, 4]
// 3. for循环(按照顺序执行,上个结束了才到下一个)
async function b(){
for(var i in arr){
// 1 2 3 4
console.log(await getdata(arr[i],Math.abs(2-i)))
}
}
b()
本文使用 mdnice 排版