async/await使用详解

Promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then,代码流程不能很好的表示执行流程。

为什么是async/await

在 es6 中,我们可以使用 Generator 函数控制流程,如下面这段代码:

 
 
  1. function* foo(x) {

  2.    yield x + 1;

  3.    yield x + 2;

  4.    return x + 3;

  5. }

我们可以根据不断地调用 Generator 对象的 next()方法来控制函数的流程。但是这样仿佛不是那么的语义化。因此,在 ES6 中封装了 Generator 函数的语法糖 async 函数,但是将其定义在了 es7 中。ES7 定义出的 async 函数,终于让 JavaScript 对于异步操作有了终极解决方案。 Async 函数是 Generator 函数的语法糖。使用 关键字 Async 来表示,在函数内部使用 await 来表示异步。相较于 Generator,Async 函数的改进在于下面几点:Generator 函数的执行必须依靠执行器,而 Async() 函数自带执行器,调用方式跟普通函数的调用一样。 Async 和 await 相较于 * 和 yield 更加语义化。 async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then()方法进行调用。

那么,我们通过一段小小的代码来说明 async/await 函数的用法:

未使用 async/await 的定时函数:

 
 
  1. fn = () => {

  2.  return new Promise((resolve, reject) => {

  3.    setTimeout(() => {

  4.      resolve(1)

  5.    }, 2000)

  6.  })

  7. }

  8. const Fn = () =>{

  9.  fn().then((res) => {

  10.    console.log(res)

  11.  })

  12.  console.log(2)

  13. }

  14. Fn()

我相信能看到这里的各位程序员大佬应该都知道这段代码的输出状况:先打印 2,2s 之后打印出 1。

使用 async/await 的定时函数:

 
 
  1. fn = () => {

  2.  return new Promise((resolve, reject) => {

  3.    setTimeout(() => {

  4.      resolve(1)

  5.    }, 2000)

  6.  })

  7. }

  8. const Fn = async () => {

  9.  await fn().then((res) => {

  10.    console.log(res)

  11.  })

  12. }

  13. Fn()

  14. console.log(2)

这一段函数的输出状况是:2s 后打印 1,然后打印 2。

那么,why?

  我们在字面上理解这两个单词 async 和 await:async 的意思是异步,async 用于定义一个异步函数,该函数返回一个 Promise。;await 的意思是等待,Promise 是一个承诺,await 也是一个承诺。Promise 的承诺是将返回值输出到 then 的回掉函数里面,无论是成功还是失败。await 的承诺是无论刮风还是下雨,我都会等你完成在做其他的步骤。因此,在上面的运用了 async/await 的代码中,会等待 fn 完全运行完成并且异步的回调完成对返回值的处理之后在开始进行下一步操作的。其原理是将异步函数转变为同步操作。

实际运用

在上周的工作中,我在一段基于 node 完成的爬虫操作中多次运用 async/await 来控制程序的执行流程:

 
 
  1. //伪代码

  2. let axiosArr = [];

  3. for (let i = 0, len = arr.length; i < len; i++) {

  4.  let params = qs.stringify({

  5.    'param': arr[i].index,

  6.  })

  7.  axiosArr.push(axios.post(url, params, {

  8.    headers

  9.  }))

  10. }

  11. /*

  12. *上面的循环是循环抓取2345条数据,平均每个数据要访问16个接口

  13. *用axios.all同时询问,当返回结束后将返回值处理

  14. *然后将返回值存储到mongodb数据库中

  15. */

  16. await axios.all(axiosArr).then(

  17.  axios.spread(function () {

  18.    for (let i = 0, len = arguments.length; i < len; i++) {

  19.      let str = `${unescape(arguments[i].data.replace(/\\u/g, '%u'))}`;

  20.      str = basics.subStr(basics.deletN(basics.deletS(basics.cutStr(str))));

  21.      concentArr[i].concent = str

  22.    }

  23.    mg.mongodbMain({

  24.      name: obj.name,

  25.      alias: obj.alias,

  26.      type: type,

  27.      url: obj.url,

  28.      drugsConcent: concentArr

  29.    })

  30.  }))

其实操作就这么点,大家看一下代码都会懂。但是问题是,当我不使用 async/await 时,会产生的情况是会先访问 2000 + 个数据,不断访问其 16 个接口,但是由于 promise 的 then 的回调函数为异步的,会挂起,而不是直接将数据存到数据库中。这貌似和我们预想的不一样啊。因此,我在这里使用了 async/await 函数,使用同步处理异步操作,将 promise 同步化,当 axios.all 访问完成这每一条数据的 16 个接口后,直接将数据存储到数据库中,然后才会走到循环的下一层,依旧是访问下一条数据的 16 个接口。

async/await 的身后事

我们说过了 async 函数返回值是 Promise 对象。

 
 
  1. const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));

  2. async function f(){

  3.    await delay(1000);

  4.    await delay(2000);

  5.    await delay(3000);

  6.    return 'done';

  7. }

  8. f().then(v => console.log(v));// 6s之后打印'done'

那么其内部一旦抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。

 
 
  1. async function e(){

  2.    throw new Error('error');

  3. }

  4. e().then(v => console.log(v))

  5. .catch( e => console.log(e));//抛出的错误会被catch捕捉到

并且,async 有一个和 promise.all 相似的特性,就是内部一点有一个 await 函数报错,后续的就不再执行了

 
 
  1. let fn1 = ()=>{

  2.    return new Promise((resolve,reject) => {

  3.        setTimeout(()=>{

  4.            reject('故意抛出错误');

  5.        },500);

  6.    });

  7. }

  8. let fn2 = ()=>{

  9.    return new Promise((resolve,reject)=>{

  10.        setTimeout(()=>{

  11.            resolve(1);

  12.        },500);

  13.    });

  14. }

  15. let getList = async ()=>{

  16.    let a = await fn1();

  17.    let b = await fn2();

  18.    return {first: a,second:b};

  19. }

  20. getList().then(result=> {

  21.    console.log(result);

  22. }).catch(err=> {

  23.    console.log(err);// 由于fn1的报错,async的状态直接变成了rejected

  24. });

当 Promise 出现的时候,我们仿佛看到了回调地狱的灭亡。当 Async/Await 出现时,异步终于不是一件困难的事情。

猜你喜欢

转载自blog.csdn.net/hyupeng1006/article/details/81033541