javaScript深入—-async&&await


theme: orange
highlight: a11y-dark

什么是 async ?

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

async的特点:

  • async 一般不单独使用,而是和 await 一起使用。
  • async 函数被调用的时候,会立即返回一个 Promise
  • async 函数执行到 await 的时候,会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 Promise 的异步操作被兑现或被拒绝之后才会恢复进程。

await的特点:

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

为什么要使用 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 的嵌套。请求一个接口数据,然后打印,就像你看到的,很简单,可阅读性更高。

(二)用同步的思路写异步逻辑

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);
        });
      });

这样就形成了最初“回调地狱”,这个代码也并不是很好理解,因为中间有一个循环的过程,而且这个执行的过程的异步的,并不像我们之前学到的一个链式调用能解决的。

接下来我们看一下 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);
          result = await reader.read();
        }
        return total;
      }

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

日常妙用 await

asyncawait 不单可以使用在请求接口上,还可以为代码适当造成”阻塞”。

有时候我们就是想要一些功能函数延迟几秒后执行:

    async function delayTwoFunt() {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve("2m,");
          }, 2000);
        });
      }
      async function firstFunt() {
        console.log("start");
        const delayRes = await delayTwoFunt();
        console.log("delayRes: ", delayRes);
        console.log("firstFunt");
      }
      firstFunt();

小心 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

以下代码的执行顺序是?

     async function foo() {
        console.log(1);
        await bar();
        console.log(2);
      }
      async function bar() {
        console.log(3);
      }
      foo();
      console.log(4);
      //1342

过程分析:

  • 首先一进来是创建了两个函数的,我们先不看函数的创建位置,而是看它的调用位置
  • 发现 foo 函数被调用了,然后去看看调用的内容
  • 执行函数中的同步代码log(1),之后碰到了 await ,它会阻塞foo后面代码的执行,因此会先去执行 bar 中的同步代码,然后 跳出 foo
  • 跳出 foo 函数后,执行同步代码 log(4)
  • 在一轮宏任务全部执行完之后,再来执行刚刚 await 后面的内容 log(2)。

在这里,你可以理解为「紧跟着await后面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then中」。

让我们来看看将await转换为 Promise.then 的伪代码:

     async function foo() {
        console.log(1);
        // 原来代码
        // await bar();
        // console.log(2);

        // 转换后代码
        new Promise((resolve) => {
          resolve();
          bar();
        }).then((res) => console.log(2));
      }
      async function bar() {
        console.log(3);
      }
      foo();
      console.log(4);
      //1342
      // 复制代码转换后的伪代码和前面的执行结果是一样的。

异常处理

try...catch

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

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

这里就不举例子了。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容