深入实现 JavaScript 中的 Promise

阅读时长 9 分钟读完

前言

Promise 是 JavaScript 中涉及异步编程的重要组成部分,它通过将异步操作封装在一个对象中,使得代码更为清晰、可读性更高。Promise 是 ECMAScript 6 规范中新增的一个 API,与同步代码类似,Promise 的特点在于它可以处理异步操作,同时可以通过链式调用的方式优雅地解决回调地狱问题。

本篇文章将会带领读者深入了解 Promise 的内部运行机制,介绍如何在实现自己的 Promise 时解决一些常见问题,并附带代码示例,供读者学习参考。

Promise 的基本用法

在深入探讨 Promise 的实现原理之前,我们先来了解一下 Promise 的基本用法。Promise 的构造函数接受一个函数作为参数,这个函数会立即执行,而其参数则表示 Promise 的两个状态:

  • resolved:表示 Promise 已经成功执行并返回一个结果。
  • rejected:表示 Promise 执行失败,抛出一个错误。

具体的 Promise 用法如下:

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

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

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

其中,then() 方法可以用来处理 Promise 对象的状态变化,它接受两个函数作为参数,当 Promise 对象由 resolved 状态转变为 rejected 状态时,第二个函数会被执行,传入 Promise 对象所抛出的错误;当 Promise 对象由 resolved 状态转变为 fulfilled 状态时,第一个函数会被执行,传入 Promise 对象所返回的结果。

每次调用 then() 方法会返回一个新的 Promise 对象,这次返回的 Promise 对象所包含的处理代码就是该方法所传入的参数函数。如果该函数返回一个值,那么这个值就会被立即作为新 Promise 对象的 value 属性,在下一次状态改变时传递给 then() 方法回调函数。如果该函数抛出一个错误,那么下一次调用 then() 方法时,传入的第二个回调函数会被立即执行。

Promise 的内部实现

在实现自己的 Promise 之前,我们需要先了解一下 Promise 的基本内部实现原理。Promise 的实现基于状态机,分别有以下三个状态:

  • pending:Promise 实例已创建,但执行操作并未完成。
  • fulfilled:Promise 实例已执行操作并返回一个结果。
  • rejected:Promise 实例执行操作失败并抛出一个错误。

在 Promise 的内部,其维护了一个内部数据结构,其中包含以下信息:

  • status:表示 Promise 当前的状态。
  • value:表示 Promise 执行完成后返回的结果。

Promise 内部是通过这些状态进行状态转移的,具体的实现如下:

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

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

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

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

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

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

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

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

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

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

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

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

如上代码所示,我们将 Promise 内部的状态记录在 this.status 字段中,并将值记录在 this.value 字段中。其中,_resolve() 和 _reject() 方法分别处理 fulfilled 和 rejected 状态。而 then() 方法则是用来处理状态转换的。

在 then() 方法中,我们首先按照当前的状态决定如何处理成功、失败回调函数,如果当前状态为 pending,则将这些回调函数分别添加到 _resolveQueue 和 _rejectQueue 中,等到状态改变时一并处理。如果当前状态为 fulfilled 或者 rejected,那么就直接调用相应的回调函数。

同时,我们还需要处理一个比较特殊的情况——回调函数本身返回一个 Promise 对象,这个时候我们需要等待这个 Promise 对象状态的改变,然后再根据其状态执行后续操作。具体的实现方式是检查回调函数的返回值是否为 MyPromise 对象,如果是,则在 MyPromise 对象状态改变时,向 this 返回一个新的 MyPromise 对象,否则就直接调用 then() 方法所传入的回调函数。

回调地狱如何用 Promise 解决?

回调地狱指的是在嵌套多个异步函数时,要进行大量的回调函数编写,导致代码可读性差,甚至难以进行调试的状况。Promise 的链式调用方式可以容易地解决这个问题:

我们可以将这些需要异步执行的函数嵌套在这样的 then() 方法的链式调用中,这样它们就不需要长时间地等待函数完成,而是可以继续执行下去,从而形成美丽的 Promise 链式调用。

此外,Promise 还可以通过它的 catch() 方法去处理前面任意一个 then() 方法中的错误信息。

Promise 的使用场景

Promise 的使用场景非常多,其中最常见的是在进行异步操作时,可以为其提供一种更好的设计方式。举个例子,我们可以写如下代码:

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

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

通过 Promise 对象,我们可以在数组中的每一个元素依次执行异步操作。这种代码形式可以用于请求 API,通过 Promise 概念,可以清晰地表达出操作顺序和操作结果。

总结

在本篇文章中,我们介绍了 Promise 的基础概念和使用方法,详细探讨了 Promise 内部运行机制和回调地狱问题的解决方法。通过了解 Promise 的实现原理并模拟实现了自己的 Promise,读者可以更深入地理解 Promise 的运行机制,并针对实际的应用场景进行全方位的掌握。

当然,除了 Promise,JavaScript 中还有许多其他的异步编程方式,例如 async/await、ESLint 等,读者可以在日后的学习中进一步深入掌握相关技术。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64534a29968c7c53b07bc901

纠错
反馈