一文带你了解async/await的使用

 更新时间:2022年09月01日 14:05:34   作者:前端小智  
async/await 让异步代码变成同步的方式,从而使代码更具表现力和可读性;还统一了异步编程的经验;以及提供了更好的错误堆栈跟踪。本文就来讲讲async/await的使用,需要的可以参考一下

开篇观点,async/await 不仅仅是 Promise 上面的语法糖,因为 async/await 确实提供了切实的好处。

  • async/await 让异步代码变成同步的方式,从而使代码更具表现力和可读性。
  • async/await 统一了异步编程的经验;以及提供了更好的错误堆栈跟踪。

关于 JS 中异步编程的一点历史

异步编程在 JavaScript 中很常见。每当我们需要进行网络服务调用、文件访问或数据库操作时,尽管语言是单线程的,但异步性是我们防止用户界面被阻塞的方法。

在 ES6 之前,回调是猿们处理异步编程的方式。我们表达时间依赖性(即异步操作的执行顺序)的唯一方法是将一个回调嵌套在另一个回调中,这导致了所谓的回调地狱

Es6 中引入了 Promise,它是一个用于异步操作的一流对象,我们可以轻松地传递、组合、聚合和应用转换。时间上的依赖性通过 then方法链干净地表达出来。

有了 Promise 这个强大的伙伴,听起来异步编程在 JS 中是一个已经解决的问题,对吗?

恩,还没有,因为有时候 Promise 的级别太低了,不太适合使用。

有时 Promise 的级别太低,不适合使用

尽管出现了 Promise,但在 JS 中仍然需要一个更高级别的语言结构来进行异步编程。

我们来看个例子, 假设我们需要某个函数在某个时间间隔轮询一个API。当达到最大重试次数时,它就会解析为 null

下面是 Promise 的一种解决方案:

let count = 0;

function apiCall() {
  return new Promise((resolve) =>
    // a在第6次重试时,它被解析为 "value"。
    count++ === 5 ? resolve('value') : resolve(null)
  );
}

function sleep(interval) {
  return new Promise((resolve) => setTimeout(resolve, interval));
}

function poll(retry, interval) {
  return new Promise((resolve) => {
    // 为了简洁起见,跳过错误处理

    if (retry === 0) resolve(null);
    apiCall().then((val) => {
      if (val !== null) resolve(val);
      else {
        sleep(interval).then(() => {
          resolve(poll(retry - 1, interval));
        });
      }
    });
  });
}

poll(6, 1000).then(console.log); // 'value'

这种解决方案的直观性和可读性取决于人们对Promise的熟悉程度,以及 Promise.resolve 如何 "平铺" Promise 和递归。对我来说,这不是写这样一个函数的最可读的方式。

使用 async/await

我们用 async/await 语法重写上述解决方案:

async function poll(retry, interval) {
  while (retry >= 0) {
    const value = await apiCall().catch((e) => {}); 
    if (value !== null) return value;
    await sleep(interval);
    retry--;
  }

  return null;
}

我想大多数人都会觉得上面的解决方案更有可读性,因为我们能够使用所有正常的语言结构,如循环、异步操作的 try-catch 等。

这可能是 async/await 的最大卖点--使我们能够以同步的方式编写异步代码。另一方面,这可能是对 async/await 最常见的反对意见的来源,稍后再谈这个问题。

顺便说一下,await甚至有正确的操作符优先级,所以await a + await b 等于(await a) + (await b),而不是让我们说await (a + await b)

async/await 在同步和异步代码中提供了统一的体验

async/await的另一个好处是,await自动将任何非Promise(non-thenables)包装成 Promises 。await的语义等同于Promise.resolve,这意味着可以 await 任何东西:

function fetchValue() {
  return 1;
}

async function fn() {
  const val = await fetchValue();
  console.log(val); // 1
}

// 上面等同于下面

function fn() {
  Promise.resolve(fetchValue()).then((val) => {
    console.log(val); // 1
  });
}

如果我们将 then 方法附加到从 fetchValue 返回的数字 1 上,就会出现以下错误。

function fetchValue() {
  return 1;
}

function fn() {
  fetchValue().then((val) => {
    console.log(val);
  });
}

fn(); // ❌ Uncaught TypeError: fetchValue(...).then is not a function

最后, 从 async 函数返回的任何东西都是一个 Promise:

Object.prototype.toString.call((async function () {})()); // '[object Promise]'

async/await 提供更好的错误堆栈跟踪

V8工程师Mathias写了一篇名为Asynchronous stack traces: why await beats Promise#then() 的文章,介绍了为什么与 Promise相比,引擎更容易捕捉和存储 async/await 的堆栈跟踪。事例如下:

async function foo() {
  await bar();
  return 'value';
}

function bar() {
  throw new Error('BEEP BEEP');
}

foo().catch((error) => console.log(error.stack));

// Error: BEEP BEEP
//     at bar (<anonymous>:7:9)
//     at foo (<anonymous>:2:9)
//     at <anonymous>:10:1

async 版本正确地捕获了错误堆栈跟踪。

我们再来看看 Promise 版本。

function foo() {
  return bar().then(() => 'value');
}

function bar() {
  return Promise.resolve().then(() => {
    throw new Error('BEEP BEEP');
  });
}

foo().catch((error) => console.log(error.stack));

// Error: BEEP BEEP  at <anonymous>:7:11

堆栈跟踪丢失。从匿名的箭头函数切换到命名的函数声明有一点帮助,但帮助不大:

function foo() {
  return bar().then(() => 'value');
}

function bar() {
  return Promise.resolve().then(function thisWillThrow() {
    throw new Error('BEEP BEEP');
  });
}

foo().catch((error) => console.log(error.stack));

// Error: BEEP BEEP
//    at thisWillThrow (<anonymous>:7:11)

对 async/await 常见反对意见

async/await 主要有两种常见的反对意见。

首先,当独立的异步函数调用可以用Promise.all并发处理时,如果我们还大量使用async/await 可能会导致滥用,这样会造成开发者不去试图了解 Promise 的幕后是如何工作,而只是一味的使用 async/await

第二种情况更为微妙。一些函数式编程爱好者认为 async/await 会招致命令式编程。从 FP 程序员的角度来看,能够使用循环和 try catch 并不是一件好事,因为这些语言结构意味着副作用,并鼓励使用不那么理想的错误处理。

我对这种说法待保留意见。FP程序员理所当然地关心他们程序中的确定性。他们希望对自己的代码有绝对的信心。为了达到这个目的,需要一个复杂的类型系统,其中包括Result等类型。但我不认为async/await本身与FP不相容。

无论如何,对于大多数人来说,包括我在内,FP仍然是一种后天的味道(尽管我确实认为FP超级酷,而且我正在慢慢学习它)。async/await提供的正常控制流语句和try catch错误处理,对于我们在 JavaScript 中协调复杂的异步操作是非常宝贵的。这正是为什么说 "async/await只是一种语法糖" 是一种轻描淡写的说法。

以上就是一文带你了解async/await的使用的详细内容,更多关于async/await的资料请关注脚本之家其它相关文章!

相关文章

  • 让JavaScript的Alert弹出框失效的方法禁止弹出警告框

    让JavaScript的Alert弹出框失效的方法禁止弹出警告框

    弹出框难免会影响你的心情,所以通过以下代码可将Js弹出框屏蔽掉,实现思路是对alert方法重写
    2014-09-09
  • Bootstrap每天必学之导航条

    Bootstrap每天必学之导航条

    Bootstrap每天必学之导航条,本文向大家讲解了多种多样的导航条,以及导航条中元素的实现方法,感兴趣的小伙伴们可以参考一下
    2015-11-11
  • echarts实现晶体球面投影的实例教程

    echarts实现晶体球面投影的实例教程

    这篇文章主要给大家介绍了关于echarts实现晶体球面投影的相关资料,文中介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • BootStrap table删除指定行的注意事项(笔记整理)

    BootStrap table删除指定行的注意事项(笔记整理)

    在前端开发中遇到这样的问题,对于table指定行的数据进行删除,花了好长时间才解决,今天小编抽时间给大家介绍BootStrap table删除指定行的注意事项,需要的朋友参考下吧
    2017-02-02
  • JavaScript操作元素实例大全

    JavaScript操作元素实例大全

    JavaScript可以对各种网页元素进行操控,下面这篇文章主要给大家介绍了关于JavaScript操作元素的相关资料,文中通过多个实例详细介绍了操作元素的方法,需要的朋友可以参考下
    2021-06-06
  • JavaScript几种弹窗事件的使用

    JavaScript几种弹窗事件的使用

    这篇文章主要介绍了JavaScript几种弹窗事件的使用,弹窗事件就是在我们执行某操作的时候,弹出信息框给出提示。或收集数据的时候,弹出窗口收集信息,不想收集可以取消隐藏,下文介绍需要的朋友可以参考一下
    2022-02-02
  • ES6入门教程之Class和Module详解

    ES6入门教程之Class和Module详解

    这篇文章主要给大家介绍了ES6中Class和Module的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-05-05
  • Javascript 函数对象的多重身份

    Javascript 函数对象的多重身份

    函数对象是javascript 中一个很特殊的对象,其特殊体现在他的多重身份上。
    2009-06-06
  • JS实现动态修改table及合并单元格的方法示例

    JS实现动态修改table及合并单元格的方法示例

    这篇文章主要介绍了JS实现动态修改table及合并单元格的方法,结合完整实例形式分析了JS动态遍历及修改table单元格的具体操作技巧,需要的朋友可以参考下
    2017-02-02
  • 一文详解如何使npm-scripts更好维护的配置方法

    一文详解如何使npm-scripts更好维护的配置方法

    这篇文章主要为大家介绍了如何使npm-scripts更好维护的配置方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06

最新评论