为什么要使用 async/await ?

async/await是ES7引入的异步编程解决方案,建立在Promise之上,提供了一种更简洁、易于理解的异步代码编写方式。通过async函数,开发者可以用同步代码的风格处理异步任务,降低了代码理解难度。然而,需要注意await可能会造成不必要的阻塞,影响性能。正确使用async/await可以提高代码可读性,例如在处理网络请求和文件读取时,避免嵌套的then调用,使得代码流程更清晰。但需谨慎处理await的阻塞问题,以优化程序执行效率。

今天讲 async/await 这组 API 。

async/await 是 ES7 的标准,Promise 是 ES6 标准,async/await 这套 API 也是用来帮助我们写异步代码的,它是构建在 Promise 之上的,有点像 Okhttp 和 Retrofit 的关系。

什么是 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 就会返回一个已经 resolve 的 Promise。

当 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)
    let readFilePromise = readFile(file)
    let networkData = await fetchPromise
    let fileData = await readFilePromise
    console.log(networkData + fileData)
}

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

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

### 异步表单验证中使用 Promise 与 async/await 的流程控制 在异步表单验证中,使用 `Promise` 和 `async/await` 可以更清晰地管理代码结构,并确保验证逻辑与其他异步操作(如网络请求)之间的顺序执行。相比传统的回调方式,`Promise` 和 `async/await` 提供了更线性的控制流,避免了“回调地狱”问题[^1]。 #### 使用 Promise 进行表单验证封装 可以将 `validate()` 方法封装为一个返回 `Promise` 的函数,使其支持链式调用或后续的异步处理: ```ts const validateForm = (): Promise<boolean> => { return new Promise((resolve) => { formRef.value.validate((valid: boolean) => { resolve(valid); }); }); }; ``` 通过这种方式,可以在后续逻辑中使用 `.then()` 或 `await` 来等待验证结果,从而决定是否继续执行提交操作或其他业务逻辑。 #### 使用 async/await 控制验证后的流程 在 `async` 函数中使用 `await` 可以使代码看起来更加同步化,提升可读性和可维护性。例如,在提交按钮点击事件中等待验证完成后再发起请求: ```ts const handleFormSubmit = async () => { const valid = await validateForm(); if (!valid) { console.log('表单验证失败'); return; } // 执行提交逻辑 await submitForm(); }; ``` 此时,`await` 会阻塞当前 `async` 函数的执行,直到 `Promise` 被解决(fulfilled 或 rejected),并根据结果继续流程[^2]。 #### 错误处理与异常捕获 为了确保程序健壮性,应使用 `try/catch` 结构来捕获可能抛出的错误: ```ts const handleFormSubmit = async () => { try { const valid = await validateForm(); if (!valid) return; await submitForm(); } catch (error) { console.error('提交过程中发生错误:', error); } }; ``` 这种模式有助于集中处理异步操作中的异常,防止未捕获的 `Promise rejection` 导致运行时崩溃[^2]。 #### 避免在非 async 函数中使用 await 需要注意的是,`await` 关键字只能在 `async` 函数内部使用。如果尝试在普通函数中直接使用 `await`,将会导致语法错误: ```ts function normalFunction() { // 语法错误:await 不能在非 async 函数中使用 const result = await somePromise(); // ❌ } ``` 因此,所有需要等待异步操作完成的逻辑都应包裹在 `async` 函数中,以确保执行顺序和状态一致性[^4]。 #### 小结 在异步表单验证中合理使用 `Promise` 和 `async/await`,不仅能够提升代码可读性,还能更好地控制异步流程。通过封装验证逻辑为 `Promise`,结合 `await` 的阻塞特性,开发者可以实现清晰、可维护的异步控制流,并有效处理错误与异常情况。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值