解开 ECMAScript 2020 异步函数背后的奥秘

面试官:小伙子,你的代码为什么这么丝滑?

前言

在 Web 前端开发中,异步函数是一个非常重要的概念。它可以帮助我们处理异步操作,比如网络请求、文件读取等,以保证 JavaScript 前端程序的流畅性和效率性。同时,ES6 也为我们带来了异步函数这个新特性,用于简化异步操作的实现,其中包括异步等待 await 和异步生成器 async/await。那么,今天我们来深入探究 ECMAScript2020中异步函数背后的实现原理。

什么是异步函数

异步函数是一个很常用的概念,指的是实现异步编程的一种方式。通常来说,编写异步代码,我们需要使用回调函数或者Promise对象,从而解决异步操作和同步操作之间的进程问题。

举个例子,假设我们在前端页面上发送一个 AJAX 请求:

--------
  ---- --------------
  ------- ------
  -------- ---------------- -
    --------------------
  --
  ------ ------------- ------- ------ -
    -------------------
  -
---

在这个例子中,我们通过回调函数来实现异步操作,当服务器响应完成后,调用了success回调函数,并将响应结果传入到它的参数中。这个方式存在一些致命的缺陷:回调地狱、代码难以读懂等等,但是ES6给我们带来了异步函数,用它可以大幅度地改善这些问题。我们来看下面这个例子:

----- -------- ----------- -
  --- -
    ----- -------- - ----- ---------------------
    ----- ---- - ----- ----------------
    ------------------
  - ----- ------- -
    -------------------
  -
-

在这段代码中,我们使用了一个异步函数getUser,并且在其中使用await来等待异步调用结束。这使得代码更加简洁,可读性更好,可以避免回调地狱和异步编程问题。当函数执行完毕后,会返回一个promise对象,其中包含对应异步操作的结果,如果操作出现问题,会抛出异常并被try-catch捕获。现在,我们可以上手来探究异步函数背后的奥秘啦!

Promise 的实现原理

要想理解异步函数,我们首先要了解 Promise 的实现原理,因为 Promise 是异步函数背后的基础。

Promise 顾名思义,它是一种解决JavaScript异步编程的方式,Promise 对象两种状态:完成或失败。或者,更准确地说,Promise 有三种状态,因为执行 Promise 的代码可以分为以下几种状态之一:

  • pending(等待态): 初始状态,既不是成功,也不是失败状态。换言之,就是Promise实例创建时所处的状态;
  • fulfilled(成功态): 意味着操作成功完成,Promise将异步操作的结果作为参数传递给它的回调函数 then() ;
  • rejected(失败态):意味着操作失败,Promise将异步操作抛出的错误作为参数传递给它的回调函数 catch() 。

现在,我们来看下Promise的实现原理:

----- ------- -
  -------------------- -
    ----------- - --------- -- -----
    ----------------- - --------- -- -----------
    --------------- - --------- -- -----------

    ----- ------- - ------- -- - -- ------
      -- ------------ --- ---------- ------
      ----------- - -----------
      ----------------- - -----
    -

    ----- ------ - ------- -- - -- ------
      -- ------------ --- ---------- ------
      ----------- - ----------
      --------------- - -----
    -

    --- - -- ----
      ----------------- -------
    - ----- --- -
      ---------
    -
  -

  ----------------- ----------- -
    -- ------------ --- ------------ - -- ------------------------------------
      ------------------------------
    -

    -- ------------ --- ----------- - -- -----------------------------------
      ---------------------------
    -
  -

  ----------------- - -- -------------
    -- ------------ --- ----------- -
      ---------------------------
    -
  -
-

在这个代码片段中,我们通过构造函数创建了一个 Promise 对象。在 Promise 对象创建时,它的状态设置为 pending,并且初始化 successValue 和 errorValue 值为空。Promise 构造函数需要接收一个executor函数,该函数接受两个参数:resolve 和 reject。这两个回调函数用于在异步操作完成时处理 Promise 的状态值。

当异步操作完成,Promise 处于 fulfilled 状态,把异步操作的结果传递给成功回调函数,在异步操作失败时,将异步操作返回的错误作为参数传递给失败回调函数,并将 Promise 对象状态设置为 rejected。

Promise 的 then 方法接收两个函数,即 onFulfilled 和 onRejected。这样,一旦 Promise 成功解决,就会调用 onFulfilled 函数,如果 Promise 失败,就会调用 onRejected 函数。catch 方法用于在 Promise 抛出错误时捕获异常,它会接收一个函数参数,该函数将错误捕获作为参数。

Async/await的实现原理

现在我们回到异步函数方面,异步函数背后实现的最重要的部分是 Promise,通过 Promise 异步函数返回一个 Promise 对象,可以帮助我们以 Promise 的方式处理异步操作。下面我们来深入探究一下 Async/await 的实现原理。

在 JavaScript 中,async 是一个关键字,它用于修饰函数,使函数返回一个 Promise 对象,这个 Promise 对象在函数执行完毕后会自动 resolve。

在函数中使用 await 关键字,可以让 JavaScript 引擎暂停函数的执行,等待 Promises 完成,并返回结果。在这个过程中,JavaScript 执行其他任务,直到 Promise 执行完毕,之后继续执行这个函数中等待的下一条语句。

下面,我们来看一个简单的 Async/await 的实现示例代码:

----- -------- -------------- -
  ----- -------- - ----- -----------
  ----- ---- - ----- ----------------
  ------ -----
-

在这个 fetchData 函数中,我们使用了两个 await 关键字,并且使用 fetch() 方法来获取 JSON 数据。fetch() 返回一个 Promise 对象,而在 await 关键字的控制下,fetch() 将以异步方式运行,直到它返回 JSON 数据。返回的 JSON 数据可以赋值给变量 const data 中,该数据表示异步数据的结果,最后返回一个 data。

现在,我们来看一下 async 函数背后的实现原理。异步函数可能在几个阶段中使用:

1.定义:将 Async 声明为函数,使该函数返回一个 Promise 对象。

2.调用:在函数调用时,返回一个已解决的 Promise 存储在一个变量中,该结果调用了函数体内的相关异步操作。

下面我们来看下面这个 async 的例子:

----- -------- ----------- -
  ----- -------- - ----- ---------------------
  ----- ---- - ----- ----------------
  ------ -----
-

当我们调用getUser()函数时,返回一个Promise对象:

----- ----------- - ---------------

在调用getUser()函数时,函数首先声明为 async 函数,返回一个Promise对象,异步地执行异步操作。JavaScript引擎需要调用异步操作来捕获异步行为并返回结果,因此在getUser()函数中,可以使用await等待操作完成,并将结果返回给调用方。

async函数背后的实现方式:async函数会在定义子执行时被转换为普通函数。异步函数体内的await语句会被转换为Promise.then 或Promise.catch 的调用,从而实现异步操作的等待。同时,async 返回的值将作为 promise 对象的解决值。

下面是一个Async/await的实现:

-------- ------------------------------- -
  -- --------
  ------ ---------- -
    -- ----------------
    ----- --- - ------------------------- ----------

    -- ------ - ------- -- ----- ----
    ------ --- ----------------- ------- -- -
      -- ------ --- ---- --------
      -------- --------- ---- -
        --- ---------------

        --- -
          -- ------- --- --------- --- --- --- ---- -----
          --------------- - -------------
        - ----- ------- -
          -- -- -- ----- -- ------ ------- -- -- -- - ------
          ------ -------------
        -

        -- --- --- ----- --- -- --- --------- ------
        ----- - ------ ---- - - ---------------

        -- ------ --- --------- ------ --------
        -- ------ -
          ------ --------------
        - ---- -
          -- ----- --- ---- ------- --- ------ ------- --- -------
          ------ ----------------------------
            -------- -------------- -
              ------------ ----
            --
            -------- ------------- -
              ------------- ----
            -
          -
        -
      -
      -- ---- ------ --- --------- -----
      ------------
    --
  -
-

在这个代码片段中,我们定义了一个 asyncToGenerator 函数,该函数接收一个生成器函数作为参数,并返回另一个函数。所有存储在 async 函数中的 await 语句会被翻译成 Promise.resolve 调用,然后在 then 或 catch 中处理结果,以实现异步操作的暂停和恢复。

结论

异步函数是 JavaScript 编程语言中一种实现异步行为的重要机制,异步函数的后面是基于 Promise 实现的。async 和 await 提供了使用异步的方式,使得异步编程的设计更加紧凑和容易阅读。现在,我们已经介绍异步函数以及异步函数背后的实现方式,并且可以在日常工作和学习中使用它们了。

完整代码(对应前面的asyncToGenerator函数的代码):

-------- ------------------------------- -
  -- --------
  ------ -------- -- -
    -- ----------------
    ----- --- - ------------------------- ----------

    -- ------ - ------- -- ----- ----
    ------ --- ----------------- ------- -- -
      -- ------ --- ---- --------
      -------- --------- ---- -
        --- ---------------

        --- -
          -- ------- --- --------- --- --- --- ---- -----
          --------------- - -------------
        - ----- ------- -
          -- -- -- ----- -- ------ ------- -- -- -- - ------
          ------ -------------
        -

        -- --- --- ----- --- -- --- --------- ------
        ----- - ------ ---- - - ---------------

        -- ------ --- --------- ------ --------
        -- ------ -
          ------ --------------
        - ---- -
          -- ----- --- ---- ------- --- ------ ------- --- -------
          ------ ----------------------------
            -------- -------------- -
              ------------ ----
            --
            -------- ------------- -
              ------------- ----
            -
          -
        -
      -
      -- ---- ------ --- --------- -----
      ------------
    --
  -
-

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/66fb7c4a44713626015d8e1c


猜你喜欢

  • 使用 Koa2 实现邮件发送、推送及异常反馈

    在开发前端应用程序时,与后端服务器进行协作是必不可少的。其中,许多应用程序需要使用邮件发送和推送通知等功能,同时还需要处理异常反馈来保证应用程序的正常运行。本文将介绍如何使用 Koa2 实现邮件发送、...

    18 天前
  • 利用 Headless CMS 和 Netlify 部署自己的博客

    在现代化技术的世界中,博客已经成为了一个非常普遍的存在。对于前端工程师而言,熟练掌握如何搭建和部署博客是一项必不可少的技能。而利用 Headless CMS 和 Netlify 部署自己的博客,已经成...

    18 天前
  • 如何正确地使用 ES9 的 String.prototype.trim() 方法

    在前端开发中,字符串处理是一个常见的任务。ECMAScript 9(ES9)引入了新的字符串方法 String.prototype.trimStart() 和 String.prototype.tri...

    18 天前
  • 如何设计RESTful API避免数据劫持

    在今天的互联网时代,Web应用程序中实现异步通信的方式不断增多,其中使用RESTful API的趋势越来越普遍。RESTful API提供了一种低耦合度、高可伸缩性以及可重用性强的网络应用程序开发方式...

    18 天前
  • Custom Elements 如何实现文件上传

    前言 随着互联网的发展,文件上传已经成为了 Web 应用中的常见行为之一。文件上传功能是很多网站的重要组成部分,比如在线编辑器、云存储等等。 在现代化 Web 应用中,自定义组件(Custom Ele...

    18 天前
  • ECMAScript 2017 中的 Array.prototype.includes() 方法如何使用

    ECMAScript 2017 中的 Array.prototype.includes() 方法如何使用? 在 ECMAScript 2016,JavaScript 规范中,引入了 Array.pro...

    18 天前
  • ES6 中的 Array.from 和 Array.of 让数组变化不停

    前言 数组是前端开发中非常重要的数据类型之一,它可以帮助我们存储数据,并进行各种操作。ES6 中提供了 Array.from 和 Array.of 方法,让数组的使用变得更加方便和灵活。

    18 天前
  • 如何解决 Promise 中的回调地狱?

    在异步编程过程中,回调地狱是很常见的问题。回调地狱指的是嵌套过多的回调函数,导致代码难以阅读和维护。Promise 是解决回调地狱的一种方式,但是 Promise 本质上仍然是异步回调,所以如何解决 ...

    18 天前
  • Hapi.js 中的用户权限管理和 RBAC 实现

    在现代 Web 应用程序中,用户权限管理及角色-基于访问控制 (RBAC) 是非常重要的一部分。Hapi.js 框架提供了内置的支持,使得我们能够方便地实现用户权限管理和 RBAC。

    18 天前
  • Angular 中可复用的组件设计与实现

    前言 Angular 是一个现代化的前端框架,它的设计与实现非常灵活,可以让我们轻松地将功能进行模块化,组件化。在本篇文章中,我们将介绍如何在 Angular 中设计和实现可复用的组件。

    18 天前
  • Kubernetes 中容器亲和性 (Affinity) 使用详解

    在 Kubernetes 中,容器亲和性是一项非常重要的功能。它可以帮助我们在集群中更好地管理容器,提高资源利用率,保证应用的高可用性等等。下面,本文将详细介绍 Kubernetes 中的容器亲和性,...

    18 天前
  • GraphQL 与 CQRS 结合的实践经验

    什么是 GraphQL? GraphQL 是一种查询语言和运行时环境,用于构建 API。它由 Facebook 在 2012 年开发,并在 2015 年开源。GraphQL 的一个重要优点是它允许客户...

    18 天前
  • Chai 和 Jasmine 的区别及使用场景对比

    前言 在 JavaScript 前端开发中,单元测试是不可或缺的一环。而在单元测试中,常常需要使用断言库来判断某些条件是否成立,从而判断测试结果是否正确。Chai 和 Jasmine 都是流行的 Ja...

    18 天前
  • 如何优化 CSS Grid 布局的性能

    CSS Grid 布局是一种强大的布局机制,可以轻松地实现复杂的布局设计。然而,过度使用 Grid 布局可能会导致性能问题。本文将介绍如何优化 Grid 布局的性能。

    18 天前
  • 对于 Jest 测试文件扩展名的探究及建议

    作为一名专业的前端开发者,了解 Jest 测试框架是必不可少的。而对于 Jest 测试文件的扩展名,我们可能会有一些疑问,在本文中,我们将探究 Jest 测试文件的扩展名以及如何为我们的项目选择合适的...

    18 天前
  • Cypress 错误解决:如何解决 Cypress 端到端浏览器测试案例失败

    Cypress 是一款非常强大的端到端浏览器测试工具,但是在使用的过程中难免会遇到一些测试案例失败的情况。本文将为大家介绍一些常见的 Cypress 失败情况及其解决方案。

    18 天前
  • 使用 Less Attribute Hack 应对 IE8

    在前端开发中,我们经常要处理兼容性问题,特别是对于老旧的 IE 浏览器。针对 IE8的问题,这篇文章将介绍一种解决方案——使用 Less Attribute Hack。

    18 天前
  • Mongoose Schema 的虚拟属性详解及用法

    在使用 Mongoose 进行 MongoDB 数据库操作的过程中,Schema 是我们必须了解的一个重要概念,它用来定义数据模型的结构和属性。而虚拟属性(Virtual)是 Schema 中一个非常...

    18 天前
  • 如何在 Hapi 中使用 Socket.io 实现实时通信

    Socket.io 是一个基于 Node.js 的实时通信框架,可方便地实现服务端和客户端之间的实时通信。而 Hapi 是一个基于 Node.js 的 Web 开发框架,它提供了一些有用的工具和插件,...

    18 天前
  • 在 ES9 中使用 obj.constructor() 函数创建对象

    在 JavaScript 中,我们通常使用对象字面量或构造函数来创建对象。但在 ES9 中,我们可以使用 obj.constructor() 函数来创建对象。这种方式可以让我们更加灵活地创建对象,并且...

    18 天前

相关推荐

    暂无文章