ES8(2017)标准引入了async函数,async/await是ES8提出的基于Promise的解决异步的最终方案。
一、async关键字
async作为一个关键字放到函数前面,用于表示函数是一个异步函数。
因为async就是异步的意思,也就表示 该函数的执行不会阻塞后面代码的执行
下面是一个async函数:
async function() timeout(){ return 'hello world'; }
//语法就是在函数前面加上async关键字来表示它是异步的。
例1:async的执行不会阻塞它后面的代码
1 async function timeout () { 2 return 'hello world' 3 } 4 5 timeout(); 6 console.log('虽然我在后面,但是我先执行了') 7 8 //输出结果:
虽然第五行调用了timeout(),但是没有任何输出。接下来我们将timeout()改成 console.log(timeout())
例2:
1 async function timeout () { 2 return 'hello world' 3 } 4 5 console.log(timeout()); 6 console.log('虽然在后面,但是我先执行了')
7 //输出结果:
第五行打印出来可以看出async函数返回的是一个promise对象,如果要获取到promise返回值,我们应该用then方法。
例3:
1 async function timeout(){ 2 return 'hello world' 3 } 4 5 timeout().then(result =>{ 6 console.log(result); 7 }) 8 console.log('虽然在后面,但是我先执行');
//输出结果:
例3的代码中获取到了“hello world”,同时timeout的执行也没有阻塞后面代码的执行。
例4:在例2代码中,控制台打印的timeout()结果中Promise有一个resolved,这是async函数内部的实现原理。如果async函数中有返回一个值,当调用该函数时,内部会调用Promise.solve()方法把它转化成一个promise对象作为返回,但如果timeout内部抛出错误呢?那么就会调用Promise.reject()返回一个promise对象,这是修改一下timeout函数:
async function timeout(flag) { if (flag) { return 'hello world' } else { throw 'my god, failure' } } console.log(timeout(true)) // 调用Promise.resolve() 返回promise 对象。 console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。
//输出结果:
如果函数内部抛出错误,promise对象有一个catch方法进行捕获。
timeout(false).catch(err =>{ console.log(err) })
二、await关键字
注意:await 关键字只能放到async 函数里面
await是等待的意思,①那么他等待的是什么?
②它后面跟着什么呢?(其实他后面可以放任何表达式,不过我们更多的是放一个返回promise对象的表达式。)
例1:现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2
1 //2s 之后返回双倍的值 2 function doubleAfter2seconds(num){ 3 return new Promise((resolve,reject) =>{ 4 setTimeout(() => { 5 resolve(2 * num) 6 },2000); 7 }) 8 } 9 10 async function testResult(){ 11 let result = await doubleAfter2seconds(30); 12 console.log(result); 13 } 14 //输出结果: 15 //调用testResult函数,2s后输出60
此段例1代码的执行过程:
①:调用testResult函数,它里面遇到了await,await表示等待,此时代码暂停在这里,不再向下执行,
②:那么①中await在等什么呢?等后面的promise对象执行完毕,然后拿到promise中resolve的值并返回,拿到返回值后,再继续向下执行。以上述例1中的代码为例,在第11行中遇到await后,代码暂停执行,等待doubleAfter2seconds(30)执行完毕,doubleAfter2seconds(30)返回的promise(line3)开始执行,2s后执行第5行resolve,并返回了值60。这时line11中才拿到返回值60,然后赋值给result,此时暂停的状态结束。
③:代码继续执行下面的console.log()语句
例2:就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?
async function testResult() { let first = await doubleAfter2seconds(30); let second = await doubleAfter2seconds(50); let third = await doubleAfter2seconds(30); console.log(first + second + third); }
//输出结果:
//6s后,控制台输出220
至此,我们可以看到,写异步代码就像写同步代码一样了,再也不需要像以前一样,等一个方法的回调之后执行的需要写到这个方法的回调方法里面,这样就方便多了。
三、总结
- async和await基本是组合使用的,async用来声明一个异步方法,返回的是一个promise对象,如果要获取到对应的返回值,就需要使用.then方法;
- await只能在async方面的里面使用,让后面的执行语句或方法要等待当前await方法的结果后才能再执行。
四、当async+await遇见forEach和for···of
1 //定义一个fetch函数模拟异步请求 2 function fetch(x){ 3 return new Promise((resolve,reject) => { 4 console.log('aaa'); 5 setTimeout(() =>{ 6 resolve(x) 7 },500 * x) 8 }) 9 } 10 11 //第一题: 12 function test(){ 13 let arr = [3,2,1] 14 arr.forEach(async item =>{ 15 const res = await fetch(item) 16 console.log(res) 17 }) 18 console.log('end') 19 } 20 test(); 21 //输出结果:aaa,aaa,aaa,end,1,2,3 22 23 //第二题 24 async function test(){ 25 let arr = [3,2,1] 26 for(const item of arr){ 27 const res = await fetch(item) 28 console.log(res) 29 } 30 console.log('end') 31 } 32 test() 33 //输出结果:aaa,3,aaa,2,aaa,1,end
为什么同样是遍历,输出结果却不一样呢?
因为for...of内部处理的机制和forEach不同,forEach是直接调用回调函数,for...of是通过迭代器的方式去遍历。
foreach的处理机制:
//参考下 Polyfill 版本的 forEach,简化后的伪代码: while(index < arr.length){ //也就是我们传入的回调函数 callback(item,index) }
for...of的处理机制
//使用迭代器写第二题(既for...of码的语法糖)等价于: async function test(){ let arr = [3,2,1] const iterator = arr[Symbol,iterator]() //for of 会自动调用遍历器函数 let res = itertor.next() while(!res.done){ const value = res.value const res1 = await fetch(value) console.log(res1) res = iterator.next() } console.log('end') }