怎样以各种姿势调用 ES8 的 Async 函数

——理解 Async 函数的本质

async/await 的介绍

Async 函数是 ES2017 内引入的新功能,js 生态圈内也出现大量支持 async 函数的类库和框架,例如 eggjs

node 对最新 ES 语法支持的对照表: https://node.green。比如我们可以发现类的装饰器目前没有任何版本支持;10.1.0 开始支持 Promise.finally。

例子:显示当前的公网 ip

传统写法

const request = require('request');
request('http://whatismyip.akamai.com', function(error, res, body) {
    console.log(body);
});

Async 写法

const request = require('request-promise-native');
const body = await request('http://whatismyip.akamai.com');
console.log(body);

优点

从以上例子可以看出,源码更适应人类思考的模式,具备更好的可读性,降低了维护难度。

兼容性

Node.js 8 及以上的版本已经支持。浏览器的兼容情况可参考 caniuse async functions。列表如下:

360安全浏览器9.1版本内核已经升级到 55,搜狗浏览器9周年版已经升级内核到 58。它们不用 Babel 的情况下也支持 async/await。

完全不支持的浏览器如下:

学习资料: ECMAScript 6 入门

  1. 阮一峰编写的《ECMAScript 6 入门》有一章专门介绍了 async 函数
  2. 同时需要理解 Promise 对象,这是正确使用 Async 函数的前提。否则容易知其然而不知其所以然。

一、如何在 async 函数里使用 setTimeout

很多人在初次接触到 async 函数时觉得很酷但又一头雾水,更多时候是复制粘贴,照葫芦画瓢。当需要在 async 函数里使用 setTimeout 的场景时,有点不知所措。

错误示范

main 函数等待两秒后返回 done

const main = async () => {
    setTimeout(function () {
        return 'done';
    }, 2000);
};
const result = await main();
console.log(result);

Codepen 里的错误示范

正确做法

const main = () => {
    return new Promise((resolve, reject)=>{
        setTimeout(function () {
            resolve('done');
        }, 2000);
    });
};
const result = await main();
console.log(result);

Codepen 里的正确做法

仔细对比两个例子,可以发现 async 函数其实就是 Promise 对象的语法糖,它是返回 Promise 对象的普通 js 函数,没有什么特别之处。

async /await 就是 Promise 的 then 调用

在 js 看来,上面的错误示范里等同于

const main = () => {
    return new Promise((resolve, reject)=>{
          setTimeout(function () {
              return 'done';
          }, 2000);
        resolve();
    });
};
const result = await main();
console.log(result);

所以不会有任何等待,直接返回了。

二、如何在普通函数里调用 async 函数

如果理解了上面的例子,那么这个问题也能解决了。

错误示范

async function test() {
    return "it works";   
}
function main() {
    const result = await test();
      console.log(result);
}
main();

将出现语法错误:

    await test();
          ^^^^
SyntaxError: Unexpected identifier
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:599:28)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

正确示范

async function test() {
    return "it works";   
}
function main() {
    test()
    .then((result)=>{
        console.log(result);
      });
}
main();

三、如何在普通函数里捕获 async 中的异常

Async 版本的异常捕获

async function test() {
    throw new Error("500");
    return "it works";   
}
async function main() {
    try{
       const result = await test(); 
    } catch (e) {
        console.log(e.toString());
    }
}

上面的例子中, main 函数捕获了 test 函数里抛出的错误。如果 main 函数不是一个 async 函数,应该怎么调用呢?再用 try/catch 就是错误的。

普通函数版本的捕获

async function test() {
    throw new Error("500");
    return "it works";   
}
function main() {
    test()
    .then((result)=>{
        console.log(result);
    })
    .catch((e)=>{
        console.log(e.toString());
    });
}
try/catch 一个 async 函数,本质就是 Promise 的 catch 调用

四、举一反三

怎样用 Async 方式调用 axios 库

如果上面的例子都能理解了,我们就能更遂心应手地编写 ES 代码了。比如想要在浏览器里使用 ajax 操作的 js 库:axios。
官方例子

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

它也没有提供 async 版本的写法,我们应该怎么改呢?

try {
  const response = await axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  });
} catch (error) {
  console.log(error);
}

怎么以 Async 方式调用 Element-UI 里的 confirm 消息

官方例子

  export default {
    methods: {
      open2() {
        this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });          
        });
      }
    }
  }

改成 Async 函数

  export default {
    methods: {
      async open2() {
        try {
            await this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            });
            this.$message({
                type: 'success',
                message: '删除成功!'
            });
        } catch (e) {
            this.$message({
                type: 'info',
                message: '已取消删除'
            });      
        }
      }
    }
  }

想通这个问题后,很多写法霍然开朗,也明白了为什么 Promise 很好的解决了 callback hell 问题。

文章评论:做一头严肃的大叫驴

根据过去的经验得出,大多数评论是毫无意义的灌水,还有一小部分内容是针对文章的补充和纠错。如果你有建议请邮件联系,我将视反馈的重要程度和自己当月的经济水平发红包奖励。当然,也不一定会回复,这取决于邮件的内容质量。