async 和 await
前言
首先我们先从字面意思来理解。async
是“异步”的简写,而 await
可以认为是 async wait
的简写(就是异步等待)。所以应该很好理解 async
用于申明一个 function
是异步的,而 await
用于等待一个异步方法执行完成。
async 起什么作用
我们要知道async
是干什么的,因为在js中所有任务都是单线程执行的,代码执行顺序都是线性的,如果遇到需要请求接口的时候,代码会一直卡到请求的时候,等带接口完成之后,才会执行下面的代码,这就是线性的,很显然,这种方式并不是很友好,如果其中有一段代码出了问题,而我们没有处理这段异常的话,代码执行就会终止,所以js中引入了一个异步任务的概念。
async
是ES7才有的与异步操作有关的关键字,和 Promise
有很大关联。
(至于Promise是什么,请移步 )
返回值:
async
函数返回一个 Promise
对象,可以使用 .then
方法添加回调函数
例:
async function helloAsync(){
return "helloAsync";
}
console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
console.log(v); // helloAsync
})
但是我们往往并不是只用一个async。
而是在async
函数中可能会有 await
表达式,async
函数执行时,如果遇到 await
就会先暂停执行,等到触发的异步操作完成后,恢复 async
函数的执行并返回解析值。
注意:
await
关键字仅在 async function
中有效。如果在 async function
函数体外使用 await
,你只会得到一个语法错误。
function testAwait(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("testAwait");
resolve();
}, 1000);
});
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
await 到底在等待什么
一般来说,都认为 await
是在等待一个 async
函数完成。不过按语法说明,await
等待的是一个表达式,这个表达式的计算结果是 Promise
对象或者其它值(换句话说,就是没有特殊限定)。
这确保了重用块或将它们从一个地方移动到另一个地方所需的独立性。
这是因为 async
函数返回一个 Promise
对象,所以 await
可以用于等待一个 async
函数的返回值——这也可以说是 await
在等 async
函数,但要清楚,它等的实际是一个返回值。注意到 await
不仅仅用于等 Promise
对象,它可以等任意表达式的结果,所以,await
后面实际是可以接普通方法调用或者直接量的。所以下面这个示例完全可以正确运行
// 这是一个普通方法
function getSomething() {
return "something";
}
// 这是一个返回Promise的方法
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
返回结果
如之前所说,await
会等到后面的 Promise
返回结果 后才会执行 async
函数后面剩下的语句。
async function fn() {
console.log(1);
await new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(2);
}, 2000);
});
console.log(3);
}
fn();
// 1
// 2 (2 秒后输出,并且后面不会继续输出 3)
那问题就又来了,为什么3并没有输出呢?因为刚才说了await
会等到后面的 Promise
返回结果 后才会执行 async
函数后面剩下的语句,而当前的Promise
对象并没有返回 resolve
或 reject
。
所以它就一直在等下去,也就一直等不到剩下的语句执行了(还挺痴情,不像有的人路过也不点个赞);
但是如果 await
后面的 Promise
返回一个 reject
状态的结果的话,则会被当成错误在后台抛出。
async function fn() {
console.log(1);
var result = await new Promise(function(resolve, reject) {
setTimeout(function() {
reject(2);
}, 2000);
});
console.log(3);
}
fn();
// 1
// Uncaught (in promise) 2 (2 秒后输出)
如上结果所示,2 秒后会抛出错误,并且 3 这个数并没有被打印,说明后面的代码也没有执行。
也就是说await
语句后面跟着的Promise
对象一旦抛出错误,也就是变成reject
状态,那么整个async
函数就会停止执行。
当然这并不是我们想要的结果,其实我们可以通过对代码进行修改。使值作为一个error
对象输出,这样代码就不会报错,但是3依旧没有打印。
async function fn() {
console.log(1);
var result = await new Promise(function(resolve, reject) {
setTimeout(function() {
reject(2);
}, 2000);
});
console.log(3);
}
fn().catch(function(error) {
console.log(error);
});
// 1
// 2 (2 秒后输出)
匿名函数
async
也可以用于申明匿名函数用于不同场景,或者嵌套使用 async
函数,如 await async
的形式,只是要在 await
后面使用 async
形式的函数的话,需要这个函数立即执行且有返回值;
let fn = async function() {
let a = await (async function() {
console.log(1);
return 2;
})();
console.log(a);
async function fn2() {
return 3;
}
console.log(await fn2());
}
fn();
// 1
// 2
// 3
回调函数
正常我们在代码里写接口调用的时候,往往是通过.then
的链式调用去取值,如果说后续接口用到了前面一个接口的返回值的话,那么我们的代码就会越写越多,缩进也越来越多。这就是我们常说的 JS 的回调地狱
例:
就比如下面这样
First(function (r1) {
Second(r1, function (r2) {
Third(r2, function (r3) {
console.log(r3);
})
})
})
解决回调地狱有很多方法,比如:Promise
对象、Generator
函数、async
函数。
这里我们只说 async
方法。
我们只需要将方法修改为 async
方法 并且在里面使用 await 即可
async function request() {
const r1 = await First()
const r2 = await Second(r1)
const r3 = await Third(r2)
console.log(r3) // r3 就是我们想要的结果
}
这样写最大的好处就是,代码结构更清晰,有更好的语义,写复杂业务的时候阅读起来更快更爽。