1. 概述
ES2017(ES8)的新特性,允许我们用同步的方式编写异步代码。async / await建立在promises之上。
2. 例子
例1:
function scaryClown() {
return new Promise(resolve => {
setTimeout(() => {
resolve('🤡');
}, 2000);
});
}
async function msg() {
//等待一个Promise处理完(执行resolve或reject)
const msg = await scaryClown();
console.log('Message:', msg);
}
msg(); // Message: 🤡
例2:
function resolveAfter2Seconds() {
return new Promise(resolve => {
console.log('promsie')
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
var result = await resolveAfter2Seconds();
console.log(result);
// expected output: 'resolved'
}
asyncCall();
/*
输出:
calling
promise
resolved
*/
await关键字会阻塞函数并等待一个Promise处理完(执行resolve或reject),然后才执行之后的语句,该关键字只能在async修饰的函数中使用。阻塞过程中不会占用任何CPU资源,因为引擎可以同时执行其他任务:执行其他脚本,处理事件等。
稍微复杂一点:
例3:
function who() {
return new Promise(resolve => {
setTimeout(() => {
resolve('🤡');
}, 200);
});
}
function what() {
return new Promise(resolve => {
setTimeout(() => {
resolve('lurks');
}, 300);
});
}
function where() {
return new Promise(resolve => {
setTimeout(() => {
resolve('in the shadows');
}, 500);
});
}
async function msg() {
//a,b,c会串行执行
//0.2秒后返回
const a = await who();
//0.2+0.3=0.5秒后返回
const b = await what();
//0.2+0.3+0.5=1秒后返回
const c = await where();
console.log(`${ a } ${ b } ${ c }`);
}
msg() //🤡 lurks in the shadows
上述的例,三个异步函数会串行执行。 如果想要三个异步函数并行执行,执行完之后再输出,可以使用Promise.all()函数:
async function msg() {
//a,b,c并行执行, 0.5秒后返回: max(0.2,0.3,0.5)
const [a, b, c] = await Promise.all([who(), what(), where()]);
console.log(`${ a } ${ b } ${ c }`);
}
msg(); //🤡 lurks in the shadows
3. 使用async的注意点
async修饰的函数返回的永远是一个Promise ,例如:
async function hello() {
//返回的将是一个Promsie对象,而不是字符串
return 'Hello Alligator!';
}
const b = hello();
console.log(b); //Promise {<resolved>: "Hello Alligator!"}
因此,如果想要获取返回的字符串,需要这样:
async function hello() {
return 'Hello Alligator!';
}
hello().then(x => console.log(x)); // Hello Alligator!
如果async修饰的函数没有返回值,它会返回一个Promise.resolve(undefined)
async function hello() {
let x
}
hello().then(x => console.log(x)); // undefined
4. 异步执行的时间点
async/await 与 promise相同,永远都是异步的(即使立马resolve)。当一个promise被处理完,它的 .then/catch/finally 处理方法会被压入一个待执行的队列(microtask queue), 当Javascript引擎从当前代码中解脱出来时,Javascript引擎从该队列中获取任务并执行它。如下代码所示,alert永远先执行:
let promise = Promise.resolve();
promise.then(() => alert("promise done"));
alert("code finished"); // this alert shows first
Javascript中除了microtask queue还有另外一个异步队列 macrotask queue用来处理事件(比如鼠标点击)。microtask拥有比macrotask更高的优先级,因此JavaScript引擎会先执行microtask queue中的任务,执行完之后才会执行macrotask queue里面的任务。如下代码所示:
setTimeout(() => alert("timeout"), 0);
Promise.resolve()
.then(() => alert("promise"));
alert("code");
- code先出现
- promise随后出现
- timeout最后出现
例2:
Promise.resolve()
.then(() => {
setTimeout(() => alert("timeout"), 0);
})
.then(() => {
alert("promise");
});
- Promise.resolve()执行完成
- setTimeout将事件压入了 macrotask queue队列
- 此时microtask queue 还有任务,因此执行alert("promise")
- 最后执行macrotask queue中的任务,alert("timeout")
总而言之: 当前代码执行完成 -> microtask queue执行 -> macrotask queue执行
5. await的行为
await后面是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值。
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
现在有一个问题,await后面如果不是Promise,它还会阻塞吗?
很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈(后面会详述)的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await
后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入promise队列(Promise的Job Queue)
例:
function testSometing() {
console.log("执行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing();//关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise((resolve)=> { console.log("promise start.."); resolve("promise");});//关键点2
promise.then((val)=> console.log(val));
console.log("test end...")
输出:
test start...
执行testSometing
promise start..
test end...
testSometing
执行testAsync
promise
hello async
testSometing hello async
本文摘录及参考自:
1. Exploring Async/Await Functions in JavaScript
2. Async/await - The Modern Javascript Tutorial
3. Microtasks and event loop
4. async function - JavaScript | MDN - Mozilla
5. 理解JavaScript 的async/await - 边城客栈- SegmentFault 思否
6. Modern Asynchronous JavaScript with Asyncand Await - Flavio Copes
7. 深入理解 JavaScript 异步系列(5)—— async await- 王..._博客园
8. JavaScript* async / await:好处、坑和正确用法 - yexun..._CSDN博客
9. Javascript中的async await - cpselvis - 博客园
10. async/await 执行顺序详解- 前端学习日记- SegmentFault 思否
网友评论