async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例,函数内允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
|
async 函数是什么 |
Async 函数是 Generator 函数的语法糖。async函数就是将 Generator 函数的星号(*)替换成async,将yield
替换成await
。
async函数对 Generator 函数的改进 |
内置执行器 |
async 函数直接调用就执行了,Generator 函数需要通过next()
调用
更好的语义 |
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
返回值是 Promise |
比操作 Generator 函数返回的Iterator方便。async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
const readFile=(path)=>new Promise((resolve,reject)=>{
setTimeout((way) => {
console.log(way);
resolve('resolved');
}, 2000,path);
})
//Generator
function* gen(){
yield readFile('gen1');
yield readFile('gen2');
}
//AsyncFunction
async function f(){
await readFile('async1');
await readFile('async2');
}
let it=gen();
it.next();
it.next();
f()
|
返回Promise 对象 |
任何函数没有设置return
语句,只要函数执行没有发生错误,默认都是return undefined
。
async函数只要没有发生异常,return
语句返回的值,会作为成功回调函数(return
value相当于resolve(value)
)的参数。
正常执行 |
📌没有return
async function f(){}//函数不设置默认值救护返回udeffiend 自动包装成promise
console.log(f())//Promise {<fulfilled>: undefined}
📌有return
async function f(){return 'ok';}
f().then(value=>{console.log(value);},err=>{})//ok
内部报错 |
async function f(){throw new Error('error')}
f();
错误捕捉 |
📌方式一:直接在async函数内部捕捉 返回值做成功回调处理
async function f(){try{
throw new Error('error')
}catch(e){
//TODO handle the exception
}}
console.log(f());//Promise {<fulfilled>: undefined}
📌方式二:对返回的Promise对象做错误处理
可以在then()
方法第二个参数做失败回调,也可以用catch()
捕捉
async function f(){throw new Error('error')}
let p=f();
p.catch((e)=>{
console.log(e); // Error: error
})
await |
await
只能放在async函数中。await
表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程。- 假如
await
表达式后面跟着的不是Promise值,会通过Promise.resolve(value)
将其转换为Promise。
function timeout(ms){
return new Promise(resolve=>{
setTimeout(resolve,ms)
})
}
async function asyncPrint(){
console.log(new Date())
await timeout(2000);
console.log(new Date())
await timeout(1000);
console.log(new Date())
return 1;
}
let value=asyncPrint();
console.log(value);
setTimeout(()=>{console.log(value);},4000)
完成后Promise状态发生改变
执行顺序:
1.asyncPrint()
进入异步函数,打印当前时间。
2.await 需要等待promise的异步操作完成,async函数交出控制权,继续回到全局代码块往下执行,打印函数返回值。此时没有执行完毕,处于pending状态。随后将定时器的函数加入事件循环队列。
3.以此类推。。。
为什么第一次和第二次查看的处于pending状态的Promise的值不一样?
第一次查看的时候还在执行!第二次查看的时候执行完成!点击查看具体属性时,控制台的数据刷新挂起了。
|
继发与并发 |
继发:等前面的异步任务执行完成,才开始执行下一个异步任务。(保证异步任务执行顺序 耗时多)
并发:前后的异步任务,谁也不用等谁,可以同时进行。
await执行Promise任务普遍都是继发的。
如何让await请求并发呢 |
异步请求在子函数中起作用,不会阻塞父函数
比如用map,forEach循环,把await放到循环体里面
请求并发后怎么顺序输出结果呢? |
前面通过map返回了一个promise数组
可以用for循环,每个promise来一次await
继发
function getStream(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
function getJson(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
async function f() {
console.log(new Date());
await getStream(2000);
await getJson(2000);
console.log(new Date());//前后间隔4s
}
f();
并发
// 写法一
async function f() {
console.log(new Date());
let [json, stream] = await Promise.all([getJson(2000), getStream(2000)])
console.log(new Date());//前后间隔2s
}
// 写法二
async function f() {
console.log(new Date());
let streamPromise = getStream(2000);
let jsonPromise = getJson(2000);
await streamPromise;
await jsonPromise;
console.log(new Date());//前后间隔2s
}
方法一:
合并多个Promise对象为一个,await
就等待一次组装后的Promise对象,其他的成员Promise对象并发。
方法二:
在异步函数中调用(同步)子函数去做异步请求,这两个(同步)子函数(是并发的)不会阻塞父函数(异步函数),子函数和父函数运行环境是各自独立的。然后获取同步方法返回的Promise对象,await只是保证这个请求已经结束。不设置await就是瞬间执行完异步函数代码块,而此时的streamPromise 和jsonPromise 还处于pending状态。
并发请求顺序输出 |
页面按顺序显示多张图片
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函数,但它是并发执行的。
map()
中每次遍历形成的{}
,该{}
是一个独立执行环境,每个请求任务要花时间,交出{}
的执行权,继续遍历下一个,形成新的{}
,几个任务同时请求,任务都还在执行,此时这几个任务关系为并发
因为只有async函数内部是继发(根据数组顺序)执行,外部不受影响。后面的for..of
循环内部使用了await,因此实现了按顺序输出。
参考文档:
async 函数