Description
据说,async 将在2016年 Q2,也就是今年的 4~6 月份于 V8 上推出,到那时,我们就可以欢快地写上『同步代码』啦!所以,本文的目的就是教大家如何使用 async。
配置 webpack + babel 使用 async
如果已配置过,请跳过本节
首先,安装babel-core
和babel-loader
,这两项分别是 babel 的核心和 babel 能够在 webpack 中运行的保障。
npm install babel-core babel-loader --save-dev
接着,安装babel-preset-stage-3
插件,就可以直接使用 async 了。
npm install babel-preset-stage-3 --save-dev
若你希望你的代码可以在浏览器,或是 node v4 以下的环境上运行,就需要加上babel-preset-es2015
,因为 babel 转码 async 的原理是将其转为 generator,低版本浏览器若要使用 generator,还需要再转成 es5 的代码。其外,若要在 react 中使用,需另加babel-preset-react
。
附一份 webpack.config.js 的 loader部分
module: {
loaders: [{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, 'src')
],
loader: 'babel-loader',
query: {
presets: ['es2015', 'stage-3', 'react'],
}
}]
}
正文
我们先来写一个最简单的 async 函数
function getDataFromGithub() {
return fetch('https://api.github.com/whatever')
.then((data) => {
return data.status;
});
}
async function githubPage() {
const data = await getDataFromGithub();
console.log(data); // 200
return data;
}
async function wrap() {
const data = await githubPage();
console.log(data); // 200
}
wrap();
done!
只需要在 function 前面加上 async 关键字,以及在被调用的函数前面加上 await 关键字,就是这么简单。
来讲讲需要注意的几点
1.首先,请谨记,async的基础是 promise,我们可以试着改写githubPage
这个函数
function githubPage() {
getDataFromGithub()
.then((v) => {
console.log(v);
});
}
对比两个版本的githubPage
函数,就能发现,await 相当于是调用了 then 方法,并拿到返回值(当然这只是打个比方,实际上 then 函数是用来注册回调的)。
2.await 关键字不允许出现在普通函数中。map((x) => await x * x)
,类似这样的代码是会报错的。map(async (x) => await x * x)
,这样的代码不会报错,但是不符合我们的预期,其中的 await 不是顺序执行而是并行地执行,至于原因,请看这里。没有办法,目前看来,只能在 for 循环中使用
async function foo() {
const arr = [bar, baz];
for (const func of arr) {
await func();
}
}
async 函数的返回值是一个 promise,也就是说,我们可以写这样的代码:
async function foo() {
return await getJSON();
}
foo.then((json) => {
console.log(json);
});
若 await 后的函数 reject 了,或是抛出了一个错,上面的代码是没有办法处理的。当然应对方法也很简单,就是把业务代码都包裹在 try/catch 中,在 catch 中对错误进行统一处理。
async function wrap() {
try {
const data = await githubPage();
console.log(data); // 200
} catch (e) {
// ...handle error
}
}
上面的代码都是顺序执行,那怎么让两个 await 同时执行呢
const [foo, bar] = await* [getFoo(), getBar];
很简单吧,不过,上面的写法已经被废弃了,得用下面这个写法:
const [foo, bar] = await Promise.all([getFoo(), getBar()]);
虽然也很好理解,但不得不吐槽这样写实在是太丑了。
当然,有 Promise.all 自然也可以用 Promise.race
const foo = await Promise.race([getFoo(), getBar()]);
async 也可以在 class 中使用:
class Foo {
constructor() {
this.index = 0;
}
async test(v) {
console.log('class A');
return await Promise.resolve(this.index++);
}
}
class Bar {
async test(foo) {
console.log('class B');
return await foo.test();
}
}
const foo = new Foo();
const bar = new Bar();
foo.test().then(v => console.log(v));
bar.test(foo).then(v => console.log(v));
// class A
// class B
// class A
// ----- next tick -----
// 0
// 1
若是像上面这样直接调用,并不会得到预期的结果,因为这相当于是不加 await 调用 async 函数。我们需要将函数调用包装一下~~(快去抢注 co-async)~~:
(async function wrapper() {
await foo.test().then(v => console.log(v));
// class A
// 0
await bar.test(foo).then(v => console.log(v));
// class B
// class A
// 1
})()