async 函数是什么

一句话,async 函数就是 Generator 函数的语法糖。如下代码:

  1. //次读取两个文件
  2. var fs = require('fs');
  3. var readFile = function (fileName){
  4. return new Promise(function (resolve, reject){
  5. fs.readFile(fileName, function(error, data){
  6. if (error) reject(error);
  7. resolve(data);
  8. });
  9. });
  10. };
  11. var gen = function* (){
  12. var f1 = yield readFile('/etc/fstab');
  13. var f2 = yield readFile('/etc/shells');
  14. console.log(f1.toString());
  15. console.log(f2.toString());
  16. };
  17. //写成 async 函数,就是下面这样。
  18. var asyncReadFile = async function (){
  19. var f1 = await readFile('/etc/fstab');
  20. var f2 = await readFile('/etc/shells');
  21. console.log(f1.toString());
  22. console.log(f2.toString());
  23. };

一比较就会发现,async 函数就是将 Generator 函数的星号()替换成 async,将 yield 替换成 await,仅此而已。
*补充:

co函数库 是一个 generator 函数的自启动执行器,使用条件是 generator 函数的 yield 命令后面,只能是 thunk 函数或 Promise 对象,co 函数执行完返回一个 Promise 对象。async、await 是 co 库的官方实现。也可以看作自带启动器的 generator 函数的语法糖。

async 的用法

同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

  1. async function getStockPriceByName(name) {
  2. var symbol = await getStockSymbol(name);
  3. var stockPrice = await getStockPrice(symbol);
  4. return stockPrice;
  5. }
  6. getStockPriceByName('goog').then(function (result){
  7. console.log(result);
  8. });
  9. //上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

下面是一个更加简单的例子:

  1. function timeout(value) {
  2. return new Promise((resolve) => {
  3. setTimeout(()=>resolve(value), 1000);
  4. });
  5. }
  6. async function asyncPrint(value) {
  7. var aa = await timeout(value);
  8. console.log(aa)
  9. return 'ceshi'
  10. }
  11. asyncPrint('hello world');
  12. //上面代码指定1000毫秒以后,输出"hello world"。
  13. //asyncPrint返回的是一个promise,所以可以在asyncPrint函数内返回一个值,然后then中获取到
  14. asyncPrint('hello chentao').then(res=>{
  15. console.log(res)//ceshi
  16. })

async/await 的优势

处理 then 链

继续上面的例子如果直接用promise实现如下:

  1. function timeout(value) {
  2. return new Promise((resolve) => {
  3. setTimeout(()=>resolve(value), 1000);
  4. });
  5. }
  6. timeout("hello word").then(function(res){
  7. console.log(res)
  8. })

这里两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

  1. /**
  2. * 传入参数 n,表示这个函数执行的时间(毫秒)
  3. * 执行的结果是 n + 200,这个值将用于下一步骤
  4. */
  5. function takeLongTime(n) {
  6. return new Promise(resolve => {
  7. setTimeout(() => resolve(n + 200), n);
  8. });
  9. }
  10. function step1(n) {
  11. console.log(`step1 with ${n}`);
  12. return takeLongTime(n);
  13. }
  14. function step2(n) {
  15. console.log(`step2 with ${n}`);
  16. return takeLongTime(n);
  17. }
  18. function step3(n) {
  19. console.log(`step3 with ${n}`);
  20. return takeLongTime(n);
  21. }

现在用 Promise 方式来实现这三个步骤的处理

  1. function doIt() {
  2. console.log("doIt");
  3. const time1 = 300;
  4. step1(time1)
  5. .then(time2 => step2(time2))
  6. .then(time3 => step3(time3))
  7. .then(result => {
  8. console.log(`result is ${result}`);
  9. });
  10. }
  11. doIt();
  12. // doIt
  13. // step1 with 300
  14. // step2 with 500
  15. // step3 with 700
  16. // result is 900

如果用 async/await 来实现呢,会是这样

  1. async function doIt() {
  2. console.log("doIt");
  3. const time1 = 300;
  4. const time2 = await step1(time1);
  5. const time3 = await step2(time2);
  6. const result = await step3(time3);
  7. console.log(`result is ${result}`);
  8. }
  9. doIt();

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

参数传递

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

  1. function step1(n) {
  2. console.log(`step1 with ${n}`);
  3. return takeLongTime(n);
  4. }
  5. function step2(m, n) {
  6. console.log(`step2 with ${m} and ${n}`);
  7. return takeLongTime(m + n);
  8. }
  9. function step3(k, m, n) {
  10. console.log(`step3 with ${k}, ${m} and ${n}`);
  11. return takeLongTime(k + m + n);
  12. }

先看promise的写法:

  1. function doIt() {
  2. console.time("doIt");
  3. const time1 = 300;
  4. step1(time1)
  5. .then(time2 => {
  6. return step2(time1, time2)
  7. .then(time3 => [time1, time2, time3]);
  8. })
  9. .then(times => {
  10. const [time1, time2, time3] = times;
  11. return step3(time1, time2, time3);
  12. })
  13. .then(result => {
  14. console.log(`result is ${result}`);
  15. console.timeEnd("doIt");
  16. });
  17. }
  18. doIt();
  19. // step1 with 300
  20. // step2 with 800 = 300 + 500
  21. // step3 with 1800 = 300 + 500 + 1000
  22. // result is 2000

再看async/await

  1. async function doIt() {
  2. console.time("doIt");
  3. const time1 = 300;
  4. const time2 = await step1(time1);
  5. const time3 = await step2(time1, time2);
  6. const result = await step3(time1, time2, time3);
  7. console.log(`result is ${result}`);
  8. console.timeEnd("doIt");
  9. }
  10. doIt();

到这里就发现,相对于promise,async的写法会简单很多。promise想要将前面操作的结果传递给后续的异步太难处理

注意点

await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。

  1. async function myFunction() {
  2. try {
  3. await somethingThatReturnsAPromise();
  4. } catch (err) {
  5. console.log(err);
  6. }
  7. }
  8. // 另一种写法
  9. async function myFunction() {
  10. await somethingThatReturnsAPromise().catch(function (err){
  11. console.log(err);
  12. });
  13. }

https://segmentfault.com/a/1190000007535316
http://www.ruanyifeng.com/blog/2015/05/async.html
本文是这两篇文章的学习笔记