brunoyang / blog

134 stars 13 forks source link

Async就要来了,来不及解释了,快上车! #14

Open brunoyang opened 8 years ago

brunoyang commented 8 years ago

据说,async 将在2016年 Q2,也就是今年的 4~6 月份于 V8 上推出,到那时,我们就可以欢快地写上『同步代码』啦!所以,本文的目的就是教大家如何使用 async。


配置 webpack + babel 使用 async

如果已配置过,请跳过本节

首先,安装babel-corebabel-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
})()
xudafeng commented 8 years ago

:+1:

superRaytin commented 8 years ago

:+1:

guotie commented 8 years ago

async的本质是generator,babel是把async编译成generator的

puncha commented 8 years ago

赞!不知道async能不能用在成员函数上?

class AsyncTest {
   async foo() {...}
}

let asyncTest = new AsyncTest();
asyncTest.foo();
`
brunoyang commented 8 years ago

@puncha 👍 多谢提醒,已在结尾补充

dengnan123 commented 6 years ago

写的很不错,现在用阿里的egg2 可以用原生async很爽