JavaScript 中的 Promise,变化与优化

阅读时长 10 分钟读完

前言

在 JavaScript 中,Promise 已经是必不可少的元素了。因为在异步操作上,Promise 比传统的回调函数要更直观、可维护性更高,而且可以解决回调地狱(callback hell)的问题。

在ES6之前,Promise对象的设计遵循了Promises/A+规范,但在ES6中,Promises/A+规范完全被全局Promise替代。

在最新版本的ECMAScript中,Promise出现了新的API——Promise.allSettled(即Promise.allSettled()方法)。那么,接下来我们就来详细学习一下Promise.allSettled吧!

Promise 基础

在ES6之前,处理异步操作的方式通常是通过回调函数来实现:

但是这种处理异步操作的方式有一个明显的缺点,就是代码嵌套太深,导致可读性非常差。

而 Promise 可以简单而优雅的解决这个问题。我们可以简单地使用 Promise 来封装异步操作:

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

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

上述代码相比于回调函数要清晰简单很多。如果出错,也可以通过 catch 捕捉到错误。

Promise.all

Promise.all 方法比较常用,在处理多个 Promise 实例时可以把多个 Promise 实例的值混合处理。Promise.all 接受一个 Promise 实例的数组作为参数,当接收到多个 Promise 实例返回值之后,会将所有实例的返回结果以数组形式返回。

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

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

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

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

在上述代码中,getUser1、getUser2、getUser3 都是异步函数,分别在 500 毫秒后分别正常返回了数字 1、2 和返回错误 3.

而在 Promise.all 的 then 函数中,我们得到了一个包含 1、2 的数组,同时用户的错误也被 catch 到了。

Promise.allSettled

与 Promise.all 不同,Promise.allSettled 方法会等待所有 Promise 实例都返回对应的状态(不论是 resolved 还是 rejected),只有所有实例的状态都被转化为 settled 后,Promise.allSettled 才会执行。

例如,在下面这个例子中,getUser1、getUser2、getUser3 都返回了正确的值,但是在 getUser4 中出错了,Promise.allSettled 依然会异步等待所有的返回,最终返回状态集合。

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

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

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

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

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

在这个例子中,我们使用了 getUser4 函数,并在其中引发了错误。但是,Promise.allSettled 还是会异步等待结果并返回一个结果状态的集合。

结果中每个对象都有一个 status 属性,该属性是 settled、fulfilled、rejected 中的一个。

我们来看看返回值:

可以看到,每个 Promise 实例的状态都被等待到之后才执行,而且其中的 fulfilled 或者 reject 状态也以对象形式被统一返回了。

为了更形象一些,我们来看看下面的代码:

这个代码在前两个 Promise 实例中是已知的。可以看到,我们使用 Promise.resolve 和 Promise 构造函数来创建返回数字 3 和字符串 'foo' 的 Promise 实例,而结果数组始终都是相同的:

Promise.allSettled 的使用

Promise.allSettled 在某些情况下比 Promise.all 更加有用。当 Promise 数组中有 Promise 实例的状态不确定时, Promise.all 会挂起并等待所有 Promise 实例都返回, 而 Promise.allSettled 不会等待所有 Promise 实例都返回,它只是等待所有 Promise 实例都准备好返回。

在下面的代码中,getUser1 和 getUser2 都返回了相应的成功信息,但是 getUser3 函数在进行异步操作的时候会引发 Error。在这种情况下,Promise.all 会被拒绝并返回一个错误的菜单,而Promise.allSettled依然会执行并返回所有值集合的状态。

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

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

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

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

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

这是包含 catch 函数的 Promise.all 和包含 then 函数的 Promise.allSettled 的输出结果:

结果 1:

结果 2:

在这个例子中,我们使用了 getUser3 函数并引发了错误。但是,在使用 Promise.all 时,我们只会得到一个错误,而使用 Promise.allSettled 时我们会得到三种不同的状态值集合。

总结

在本文中,我们详细学习了 JavaScript 中的 Promise 及其基本使用和优化的知识。同时,我们重点介绍了 Promise.allSettled 方法,因为对于处理异步操作时,这个方法比 Promise.all 更加实用和有意义。

要使得代码更加简洁和可读,同时所处理的异步操作序列更加优美和直观,我们可以结合 Promises 与 Promise.allSettled,在 Promise 链中嵌入 Promise.allSettled 来获得异步过程中的更多信息。

无论是 Promise.all 为多个 Promise 实例返回值的混合处理,还是 Promise.allSettled 提供了一个比 Promise.all 更细致更完整状态集合,他们恰好在不同情形下都有优越之处。

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

纠错
反馈