async与await

async 与 await

解决了什么问题

在 async/await 之前,我们有三种方式写异步代码

  • 嵌套回调
  • 以 Promise 为主的链式回调
  • 使用 Generators

async/await 特点

  • async/await 更加语义化,async 是“异步”的简写,async function 用于申明一个 function 是异步的; await,可以认为是 async wait 的简写, 用于等待一个异步方法执行完成;
  • async/await 是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)
  • 可以通过多层 async function 的同步写法代替传统的 callback 嵌套
  • async 定义的函数的返回值都是 promise,await 后面的函数会先执行一遍,然后就会跳出整个 async 函数来执行后面 js 栈的代码

async function 语法

  • 自动将常规函数转换成 Promise,返回值也是一个 Promise 对象
  • 只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数
  • 异步函数内部可以使用 await
1
async function name([param[, param[, ... param]]]) { statements }

await 语法

  • await 放置在 Promise 调用之前,await 强制后面点代码等待,直到 Promise 对象 resolve,得到 resolve 的值作为 await 表达式的运算结果
  • await 只能在 async 函数内部使用,用在普通函数里就会报错
1
[return_value] = await expression;
  • expression: 一个 Promise 对象或者任何要等待的值。
  • 返回值:返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function pm() {
// pm函数返回一个promise对象
return new Promise((resolve, reject) => {
resolve("promise value:1");
});
}

async function test() {
let a = await pm(); // 等待一个promise对象,返回promise对象的处理结果(这里是resolve('promise value:1'))
let b = await "not promise value:2"; // 等待一个字符串,立即返回该值本身
console.log(a);
console.log(b);
return a + b;
}

test(); // 执行这个async声明的异步函数test

// promise value:1
// not promise value:2
// 返回 promise对象 {<resolved>:"promise value:1not promise value:2"}

错误处理

在 async 函数里,无论是 Promise reject 的数据还是逻辑报错,都会被默默吞掉,所以最好把 await 放入 try{}catch{}中,catch 能够捕捉到 Promise 对象 rejected 的数据或者抛出的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("error");
}, ms); //reject模拟出错,返回error
});
}

async function asyncPrint(ms) {
try {
console.log("start");
await timeout(ms); //这里返回了错误
console.log("end"); //所以这句代码不会被执行了
} catch (err) {
console.log(err); //这里捕捉到错误error
}
}

asyncPrint(1000);

如果不用 try/catch 的话,也可以像下面这样处理错误(因为 async 函数执行后返回一个 promise)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {reject('error')}, ms); //reject模拟出错,返回error
});
}

async function asyncPrint(ms) {
console.log('start');
await timeout(ms)
console.log('end'); //这句代码不会被执行了
}

asyncPrint(1000).catch(err => {
console.log(err); // 从这里捕捉到错误
});

如果你不想让错误中断后面代码的执行,可以提前截留住错误,像下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("error");
}, ms); //reject模拟出错,返回error
});
}

async function asyncPrint(ms) {
console.log("start");
await timeout(ms).catch((err) => {
// 注意要用catch
console.log(err);
});
console.log("end"); //这句代码会被执行
}

asyncPrint(1000);

使用场景

多个 await 命令的异步操作,如果不存在依赖关系(后面的 await 不依赖前一个 await 返回的结果),用 Promise.all()让它们同时触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function test1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
}

function test2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 2000);
});
}

async function exc1() {
console.log("exc1 start:", Date.now());
let res1 = await test1();
let res2 = await test2(); // 不依赖 res1 的值
console.log("exc1 end:", Date.now());
}

async function exc2() {
console.log("exc2 start:", Date.now());
let [res1, res2] = await Promise.all([test1(), test2()]);
console.log("exc2 end:", Date.now());
}

exc1();
exc2();
  • exc1 的两个并列 await 的写法,比较耗时,只有 test1 执行完了才会执行 test2
  • 比较发现 exc2 的用 Promise.all 执行更快一些

项目中使用

  • 通过 babel 来使用。
  • 只需要设置 presets 为 stage-3 即可。

安装依赖

1
npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime

修改.babelrc

1
2
"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]