async/await 是一种特殊的语法,能够更好的处理promise,可以让你编写基于Promise的代码像同步一样。
async
async 关键字放在函数之前,使得该函数总是返回一个promise对象。如果代码中显式 return 了一个值,那么函数的返回值将会被自动包装成resolved状态下的promise对象。
例如:
async function fn() {
return 1;
}
fn().then(res => {
console.log(res) // 1
})
async函数的使用方式
// 函数声明式
async function fn() { }
// 函数表达式
const fn = async function () { }
// ArrowFunc
const fn = async () => {}
// 对象中定义
let obj = {
async fn() {}
}
当async函数被调用执行的时候,会返回一个Promise的实例。当函数返回值时,promise会执行。如果函数在执行中抛出错误,那么会进入promise的rejected流程。
比如:
const foo = async () => {
throw new Error('err');
}
foo()
.then(res => {
console.log(res);
})
.catch(err=> {
console.log(err) // Error: err
})
await
await关键字只能在async函数中使用。可以用来等待Promise状态变成resolved并有返回值。await后面通常跟的是一个promise对象,如果不是,会立即被包装成resoled状态的promise。
async function foo() {
let result = await 'ok'
console.log(result); // ok
}
foo();
- 当用变量接收await的返回值时,值为promise为resolved状态下的值。
const foo = async () => {
let result = await Promise.resolve('ok');
console.log(result); // ok
}
foo();
- 函数执行中,遇到await,会首先返回promise,状态为pending,并且下面的代码会停止执行,待awiat后面的Promise执行完毕后才继续执行下面的代码,直到函数体中的代码全部执行完毕后,函数返回的promise状态才变成resolved。
function bar() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 1000);
})
}
async function foo() {
let result = await bar();
console.log(result); // ok
console.log('last');
}
console.log(foo());
输出顺序:
首先打印出pending状态的promise对象。
等到await后面的异步函数执行完后,才依次打印出ok、last,同时Promise的状态成为resolved。
由此可以看出,当在async函数中运行,当遇到await关键字时,会等待关键字后面的函数执行完毕才运行后面的代码。当所有的执行完后函数返回的Promise对象的状态才变成resolved。
异常处理
我们知道在promise中是通过catch来捕获异常的。但是在async中则使用try/catch来捕获异常。
- 如果await后面的 promise 正常resolve,await promise便会返回结果。但是在reject的情况下,便会抛出异常,并且这种异常需要用try/catch来捕获,否则会导致进程崩溃。
const baz = () => {
return Promise.reject('Oops');
}
const foo = async () => {
try {
await baz();
} catch(e) {
// e 为 baz()返回的promise对象中 reject出来的值
console.log(e); // Oops
}
}
foo();
- 如果try中有多个await,其中一个await后面的promise为reject,那么在catch中会抛出第一个异常。
如下:
// 异步操作,返回promise对象
const bar = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('Oops bar error');
}, 1000)
})
}
const baz = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('Oops baz error');
}, 1000)
})
}
const foo = async () => {
try {
await bar();
await baz();
} catch(e) {
// e 为 bar()返回的promise对象中 reject出来的值
console.log(e); // Oops bar error
}
}
foo();
由此可以如果有多个 await 后面的promise都为reject状态时,只能捕获第一个异常。
- 以下这种方式都会捕获到。由于是同步的效果,所以第二次捕获的异常是第一次捕获1s后。
const fn = async () => {
try {
await bar();
} catch (e) {
console.log(e) // Oops bar error
}
try {
await baz();
} catch (e) {
console.log(e) // Oops baz error
}
}
fn();
// 第二次和第一次的顺序相隔1s
- 如果await后面的promise中抛出异常,那么等同于async函数返回的 Promise 对象被reject。如下:
async function foo() {
await new Promise((resolve, reject) => {
throw new Error('Oops');
});
}
foo()
.then(res => console.log('ok', res))
.catch(err => console.log('faile', err)) // faile Error: Oops
- try里边的promise中的异常不会在catch中捕获,因为异常发生在promise中,只能通过promise的catch()捕获。
const foo = () => {
return new Promise((resolve, reject) => {
resolve('ok');
})
}
const bar = () => {
try {
foo()
.then(res => {
let data = JSON.parse(res);
}).catch(err => {
console.log(err); // SyntaxError: Unexpected token o in JSON at position 0
})
} catch(e) {
// 这里不会捕获到promise中的异常
console.log(e);
}
}
bar();
并行和串行
- 串行
如果有多个await,那么会按照顺序一个个的执行。
const p1 = async () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({ name: 'Rose' })
}, 1000);
})
}
const p2 = async () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({ name: 'Rose' })
}, 1000);
})
}
const getInfo = async () => {
console.time('total time');
let p1Info = await p1();
let p2Info = await p2();
console.timeEnd('total time'); // total time: 2006.705810546875ms
}
getInfo()
因为每个await后面的异步函数都会延时1s,所以总耗时大于2s。
- 并行
如果有多个await,那么先将await后面的异步函数的返回值保存变量中。
const getInfo = async() => {
console.time('total time');
const [p1Info, p2Info] = await Promise.all([
p1(),
p2()
])
console.timeEnd('total time'); // total time: 1003.5810546875ms
}
getInfo()
Promise.all()返回值是一个新的promise对象,值为数组。
- 循环中的串行
const p = async (name) => {
return new Promise(resolve => {
let user = {name};
setTimeout(() => {
resolve(user)
}, 1000)
})
}
let arr = ['bob', 'mike'];
const getInfo = async() => {
console.time('total time');
for (let name of arr) {
const p3Info = await p(name);
// 每隔1s输出一次
console.log(p3Info.name);
}
console.timeEnd('total time'); // total time: 2007.77783203125ms
}
输出顺序:
bob
mike
total time: 2007.77783203125ms
- 循环中串行改并行
const getInfo = async() => {
console.time('total time');
// 将所有的promise对象保存在数组中
let promises = arr.map(name => {
return p3(name);
})
for(let promise of promises) {
const Info = await promise;
console.log(Info.name);
}
console.timeEnd('total time'); // total time: 1006.291015625ms
}
总结:
async函数之前的关键字有两个作用:
- 使它总是返回一个promise。
- 允许在其中使用await。
await promise之前的关键字使得JavaScript等待,直到这个promise的状态为resolved
- 如果是reject,则产生异常,需通过try/catch捕获。
- 否则,它返回结果,所以我们可以将它的值赋值给一个变量。
async/await使我们少写promise.then/catch,但是不要忘记它们是基于promise的。
此外,网上很多关于promise的文章都会提到ajax的回调地狱,以此来说明promise的诞生只是用来解决异步的,其实不然。promise 只是解决异步的一种方式。
如果使用async/await,不仅代码简介,甚至有同步的feel。
promise是一种语法、一种形式,目前很多东西都是基于promise实现的,比如:jquery中的ajax,fetch,以及vue react中的很多功能也都使用了promise。所以promise是最最基本的。