async/await 详解

背景

promise的出现对于异步编程是一个跨越式的提高,但是往往在实际业务中存在很多更加复杂的流程,promise还是无法满足我们的需要,这时候在ES7中提出了async函数

概念

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await来表示异步,await关键字只能用在async定义的函数内。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,对于Generator 不熟悉的可以移步Generator 函数

想较于 Generator,Async 函数的改进在于下面四点: 错误处理

  • 内置执行器:Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
  • 更好的语义:asyncawait 相较于 *yield 更加语义化
  • 更广的适用性:co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)
  • 返回值是 Promise:async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用

优势

对于promise的优势有哪些方面

1.语法简洁,同步代码执行异步操作

//promise写法
makeRequest().then((result) => {functiona()})
             .then((result) => {functionb()})
             .then((result) => {functionc()})
             .then((result) => {functiond()})
             ....
// async
async makeRequest(){
    await functiona()
    await functionb()
    await functionc()
    await functiond()
}

2. 错误处理

promise中,try/catch不能处理promise内部的错误,因为promise对象抛出的错误不会传递到外层。所以捕获异常需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。

//promise捕获错误
const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // JSON.parse可能会出错
        const data = JSON.parse(result)
        console.log(data)
      })
      // 处理异步代码的错误
      .catch((err) => {
        console.log(err)
      })
  } catch (err) { //此处catch无效
    console.log(err)
  }
}

//async捕获错误
const makeRequest = async () => {
  try {
    // this parse may fail
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

使用aync/await的话,可以捕获promise内部错误,这里有一点需要注意,当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

 所以要把第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。但是在实际业务中有很多个await要处理呢

async function f() {
    try {
        await fetchDataA()
    } catch(err) {
        console.log('err is ->', err)
    }

    try {
        await fetchDataB()
    } catch(err) {
        console.log('err is ->', err)
    }

    try {
        await fetchDataC()
    } catch(err) {
        console.log('err is ->', err)
    }
    
    .....
}

相信这样的代码很难忍受,如果有多个await命令,可以统一放在try...catch结构中

async function main() {
  try {
    const val1 = await fetchDataA();
    const val2 = await fetchDataB();
    const val3 = await fetchDataC();
  }
  catch (err) {
    console.error(err);
  }
}

注意,如果多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

//getFoo完成以后才会执行getBar,比较耗时
let foo = await getFoo();
let bar = await getBar();

// 不存在先后关系,可以同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

3.代码调试

const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })

Promise链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们;错误栈中唯一的函数名为callAPromise,然而它和错误没有关系。(文件名和行号还是有用的)。然后当你想打断点去具体调试的时候,会发现不能在返回表达式的箭头函数中设置断点

const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at makeRequest (index.js:7:9)
  })

async/await中的错误栈会指向错误所在的函数,并且可以自行设置断点进入具体的函数内部去观察代码运行,这一点在分析生产环境的错误日志时会非常有用

4.依赖于之前异步函数的处理

比如调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。

//代码可读性差
const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {      
      return promise3(value1, value2)
    })
}

//使用async/await的话,代码会变得异常简单和直观。
const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

5.条件判断

比如需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。

//promise
const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

//async/await
const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值