为什么要使用 async/await ?

476 阅读5分钟

img.image

今天就讲 async/await 这组 API

async/awaitES7 的标准,PromiseES6 标准,async/await 这套 API 也是用来帮助我们写异步代码的,它是构建在 Promise 之上的,有点像 OkhttpRetrofit 的关系

什么是 async ?

async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await doSomeThing()
  }
  catch (rejectedValue) {
    // …
  }
}

大概长上面这个样子。async 一般不单独使用,而是和 await 一起使用,一个 async 函数内部可能有 零个 或者 多个 await

这段代码即使没有学过 Promise 也很容易看懂。这就是 async 函数的优势所在。

async 函数被调用的时候,会立即返回一个 Promise

await

await 不能单独使用,如果在非 async 函数内部被调用会报错。await 后面一般跟一个 Promise ,也可以是其他的,比如一个数值,或者一个变量,或者一个函数。如果 await 后面不是一个 Promise 就会返回一个已经 resolvePromise

async 函数执行到 await 的时候,会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 Promise 的异步操作被兑现或被拒绝之后才会恢复进程。

当然,async 函数也会返回一个 Promise ,也就是说,await 后面也可以跟一个 async 函数。

async/await 这套 API ,感觉有 Kotlin 中的 协程 和 挂起函数 内味。

为什么要使用 async?

隐藏 Promise ,更易于理解


假设我们想请求一个接口,然后把响应的数据打印出来,并且捕获异常。用 `Promise` 大概是这样写:
function logFetch(url) {
  return fetch(url)
    .then(response => response.text())
    .then(text => {
      console.log(text)
    }).catch(err => {
      console.error('fetch failed', err)
    })
}

如果用 async 函数来写,大概是这个样子:

async function logFetch(url) {
  try {
    const response = await fetch(url)
    console.log(await response.text())
  }
  catch (err) {
    console.log('fetch failed', err)
  }
}

虽然代码的行数差不多,但是代码看起来更加简洁,少了很多 then 的嵌套。请求一个接口数据,然后打印,就像你看到的,很简单。

就像刚才的那个例子,如果没学过 Promise 也不影响你看懂和理解 async 代码。这个好像没什么,但是对于一个大型的项目,人员的技术水平参差不齐,不能保证所有人都熟悉和理解 Promise ,我们要做的是尽可能的降低代码的理解难度,使大部分人都能看懂

用同步的思路写异步逻辑


`async/await` 最大的优势就是我们可以用同步的思路来写异步的业务逻辑,所以代码整体看起来更加容易看懂。我们举个例子。

上面的代码还是比较简单,我们再举一个复杂一点的例子。

我们想获取一个网络资源的大小,如果使用 Promise 大概可能是这个样子:

function getResponseSize(url) {
  return fetch(url).then(response => {
    const reader = response.body.getReader()
    let total = 0

    return reader.read().then(function processResult(result) {
      if (result.done) return total
      const value = result.value
      total += value.length
      console.log('Received chunk', value)
      return reader.read().then(processResult)
    })
  })
}

即使你学过 Promise ,这个代码也并不是很好理解,更别说是没有学过 Promise 的同学了。因为中间有一个循环的过程,而且这个执行的过程的异步的,并不想我们之前学到的一个链式调用能解决的。当然,你也可以抽取一下,使代码更简洁一点。

const processResult = (result) =>{
  if (result.done) return total
  const value = result.value
  total += value.length
  console.log('Received chunk', value)
  return reader.read().then(processResult)
}

function getResponseSize(url) {
  return fetch(url).then(response => {
    const reader = response.body.getReader()
    let total = 0
    return reader.read().then(processResult)
  })
}

但是多了一个方法,勉勉强强吧。但是我们看一下 async 函数是怎么处理的。

async function getResponseSize(url) {
  const response = await fetch(url)
  const reader = response.body.getReader()
  let result = await reader.read()
  let total = 0
  while (!result.done) {
    const value = result.value
    total += value.length
    console.log('Received chunk', value)
    // get the next result
    result = await reader.read()
  }
  return total
}

OHHHHHHHHH

这个是不是看起来就更加流畅了?因为 await 表达式会阻塞运行,甚至可以直接阻塞循环,所以整体看起来像同步的代码,也更符合直觉,更容易读懂这个代码。

小心 await 阻塞

由于 await 能够阻塞 async 函数的运行,所以代码看起来更像同步的代码,更容易阅读和理解。但是要小心 await 阻塞,因为有些阻塞是不必要的,不恰当使用可能会影响代码的性能。

假如我们要把一个网络数据和本地数据合并,错误的实例可能是这样子:

async function combineData(url, file) {
    let networkData = await fetch(url)
    let fileData = await readeFile(file)
    console.log(networkData + fileData)
}

其实我们不用等一个文件读完了,再去读下个文件,我们可以两个文件一起读,读完之后再进行合并,这样能提高代码的运行速度。我们可以这样写:

async function combineData(url, file) {
    let fetchPromise = fetch(url)
      , readFilePromise = readFile(file)
      , networkData = await fetchPromise
      , fileData = await readFilePromise
    console.log(networkData + fileData)
}

这样的话,就可以同时 网络请求 和 读取文件 了,可以节省很多时间。这里主要是利用了 Promise 一旦创建就立刻执行的特点,不懂的同学可以复习一下上一节的内容。

当然,如果你熟悉 Promise 的话,可以直接使用 Promise.all 的方式来处理,或者 await 后面跟 Promise.all 这里就不展开讲了。

异常处理

try...catch

async 函数中,异常处理一般是 try...catch ,如果没有进行 try...catchawait 表达式一旦 rejectasync 函数返回的 Promise 就会 reject

其实结合 Promise 来看,如果一个 Promise 状态敲定为 reject ,并且后续的 then 没有传入 reject 函数,或者没有 catch ,那么就会抛出异常。从这个角度来看,在 async 函数中用 try...catch 来包住 await 表达式,可能就是 catch 住这个异常,并且把这个 reject 信息传到 catch 里面。 这里就不举例子了。

链接:juejin.cn/post/696700…
来源:掘金