一、先简单的介绍一下async和await
ES7 提出的async 函数,终于让 JavaScript 对于异步操作有了终极解决方案。async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。
- async/await 是一种编写异步代码的新方法。之前异步代码的方案是回调和 promise。
- async/await 是建立在promise 的基础上
- async/await 像 promise 一样,也是非阻塞的。
- async/await让异步代码看起来、表现起来更像同步代码。
async和await还有一个很有意思的语法规定,就是这两个会同时出现,也就是说await只能出现在async函数中。
那么这里我们抛出一个疑问:那这个 async 函数应该怎么调用?
1.async怎么处理返回值
async 函数负责返回一个 Promise 对象
如果在async函数中 return 一个直接量,async 会把这个直接量通过Promise.resolve() 封装成 Promise 对象;
如果 async 函数没有返回值,它会返回 Promise.resolve(undefined)
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);
//输出结果: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello async"}
可以看出:async 函数返回的是一个 Promise 对象。 如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
如果没有返回值:
async function testAsync1() {
console.log("hello async");
}
let result1 = testAsync1();
console.log(result1);
//输出结果: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
结果返回Promise.resolve(undefined)。
所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
await 在等待什么
一般我们都用await去等带一个async函数完成,不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值,所以,await后面实际可以接收普通函数调用或者直接量>
如果await等到的不是promise对象,那跟着的表达式的运算结果就是它等到的东西;
如果是promise对象,await会阻塞后面的代码,等promise对象resolve,得到resolve的值作为await表达式的运算结果虽然await阻塞了,但await在async中,async不会阻塞,它内部所有的阻塞都被封装在一个promise对象中异步执行。
2.async/await 优势
来一个例子比较一下then()链和async/awai:
执行三个步骤,每一个步骤都需要之前步骤的结果。
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
1.先用 .then()的方法
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
看起来就非常复杂!不仅要处理一堆的参数,还非常容易在逻辑上出错。
2.用 async/await 来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
这样逻辑就非常清晰了,浅显易懂,还便于维护!
二、Async 函数的错误处理
async 函数的语法不难,难在错误处理上。
先来看下面的例子:
let a;
async function f() {
await Promise.reject('error');
a = await 1; // 这段 await 并没有执行
}
f().then(v => console.log(a));
如上面所示,当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。
解决办法:可以添加 try/catch。
// 正确的写法
let a;
async function correct() {
try {
await Promise.reject('error')
} catch (error) {
console.log(error);
}
a = await 1;
return a;
}
correct().then(v => console.log(a)); // 1
如果有多个 await 则可以将其都放在 try/catch 中。
三、那怎么样在项目中使用呢
依然是通过 babel 来使用。
只需要设置 presets 为 stage-3 即可。
安装依赖:
npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime
修改.babelrc:
"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]
这样就可以在项目中使用 async 函数了。
四、总结
使用 async / await, 搭配 promise, 可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性。
Async Await 的优点:
- 解决了回调地狱的问题
- 支持并发执行
- 可以添加返回值 return xxx;
- 可以在代码中添加try/catch捕获错误