在学习 async
之前,必须了解 Promise
和 Generator
,这两个是基础。
Promise简单回顾
Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。 Promise 简单来说就是一个容器,里面保存着一个未来才会结束的事件(通常是个异步操作)的结果,Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的的状态改变后会立即调用回调函数,回调函数通过 then()
指定,then()
会返回一个新的 Promise 对象实例,因此可以链式调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const promise = new Promise(function(resolve, reject) {
// ... 异步操作
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
// Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// 状态从pending(进行中)-> fulfilled(已成功),value 为 resolve 的参数
}, function(error) {
// 状态从pending(进行中)-> rejected(已失败),error 为 reject 的参数
// 此回调函数可以省略
}).then(() => {
// 链式调用,等待上一个 then 完成后触发
// 采用箭头函数写法,更简洁
}, () => {
});
Generator简单回顾
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。可以把它理解为一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function关键字与函数名之间有一个星号,函数体内部使用yield表达式
function* helloWorldGenerator() {
yield 'hello';
const end = yield 'world';
return end;
}
// 调用生成器函数返回一个指向内部状态的遍历器对象(Iterator Object)
var hw = helloWorldGenerator();
// 调用next(),函数从头部执行到第一个 yield 表达式,返回一个状态
hw.next(); // { value: 'hello', done: false }
// 调用next(),执行到第二个 yield 表达式
hw.next(); // { value: 'world', done: false }
// 调用next(),传入到参数会作为上一个 yield 表达式到返回值,也即是 end 会被赋值 'ending'
hw.next('ending'); // { value: 'ending', done: true }
// 状态变成 true 之后,再调用 next(),都会返回 { value: undefined, done: true }
hw.next(); // { value: undefined, done: true }
从应用上,可以把生产器函数当作分步执行到函数,每次到 yield 表达式就暂停,返回一个状态,然后手动next触发执行下一步,直到完成。
async函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数是什么?一句话,它就是 Generator 函数的语法糖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 生成器函数
const genRead = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// async函数
const asyncRead = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
语法上一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
但是 async 函数对 生成器函数的改进体现在以下四点
-
内置执行器
Generator 函数的执行必须靠执行器,调用 next() 一步步直到完成,而 async 函数跟普通函数调用方式一致,内置执行器,会自动运行到最后结果。
-
更好到语义
async和await,比起星号和yield,语义更清楚了。
- 更广的适用性
-
返回值是 Promise
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
1 2 3 4 5 6 7 8 9 10
// 定义一个异步函数 async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } // 调用异步函数,会返回一个 Promise ,可以使用 then 添加回调函数 getStockPriceByName('goog').then(function (result) { console.log(result); });
async函数返回一个 Promise 对象,async函数内部return语句返回的值,会成为then方法回调函数的参数。
1
2
3
4
5
async function f() {
return 'hello world';
}
f().then(v => console.log(v)) // "hello world"
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
1
2
3
4
5
6
7
8
9
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。
async 使用注意点
-
await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); }
-
多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
1 2 3 4 5 6 7 8 9
// 写法一,使用 Promise 到 all 函数 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二,将 getFoo和getBar 的返回对象 Promise 赋值给变量,两个会立即执行 let fooPromise = getFoo(); let barPromise = getBar(); // await 直接等待 Promise 执行结果 let foo = await fooPromise; let bar = await barPromise;
-
await命令只能用在async函数之中,如果用在普通函数,就会报错。
以上是对 async 函数的简单笔记,理解了就能进行基本的应用,足够我理解 antd pro 里面的代码了,如果要深入理解,还需详细阅读教程文档。