什么是 Promise
Promise 的概念在 JavaScript 中正式被引入是在 ES6(ECMAScript 2015) 标准中。
Promise 是 JavaScript 中用于处理异步操作的核心工具之一。它提供了一种更加优雅和可控的方式来管理异步任务,避免了传统回调函数的复杂性(即回调地狱)和可读性问题。通常用于处理网络请求、数据库操作、文件操作等。
回调地狱(Callback Hell)
在 Promise 出现之前,JavaScript 的异步操作主要依赖回调函数。回调函数虽然简单,但在复杂的异步场景中会导致代码难以维护和阅读,这种现象被称为“回调地狱”。
使用 Promise 可以简化代码。
使用 Promise
Promise 状态
Promise 有三种状态:
- Pending(进行中):初始状态,既不是成功,也不是失败。
- Fulfilled(已成功):操作成功完成,可以获取操作的结果。
- Rejected(已失败):操作失败,可以获取失败的原因。
Promise 的状态只能从 Pending
转变为 Fulfilled
或 Rejected
,并且状态一旦改变就不可逆。调用 resolve
或 reject
会将 Promise 的状态从 Pending
转变为 Fulfilled
或 Rejected
,并触发后续的回调函数。
构造一个 Promise
在 JavaScript 中,Promise 构造函数接受一个称为 executor 的函数作为参数,当你创建一个新的 Promise 实例时,这个 executor 函数就会自动运行。executor 函数被称为“执行器”,因为它负责执行异步操作。
executor 函数接受两个参数:
resolve
: 一个函数,用来将 Promise
标记为成功,并传递结果值。
reject
: 一个函数,用来将 Promise
标记为失败,并传递错误信息。
这两个函数都是由 JavaScript 自身提供的回调。executor 只能调用一个 resolve
或一个 reject
。
获取结果
为了获取 Promise
的结果,可以使用 .then
方法。
.then
方法接受两个回调函数:一个用于处理成功的结果,一个用于处理失败的情况(可选)。该方法通常用于处理 Promise
成功的结果。
如果我们只对成功完成的情况感兴趣,为 .then
提供一个函数参数即可。
Promise 是支持链式调用的。
错误处理
错误捕获
通常我们使用 .catch
捕获并处理错误。.catch
可以捕获以下几种错误:
- 显式抛出的错误(使用 throw)
- Promise 中的 reject
- 运行时错误(如 TypeError、ReferenceError 等)
- Promise 链中任何位置的错误
throw new Error
和 reject
在功能上是等效的,都会使 Promise 进入 rejected 状态。
还有一种捕获错误方法使用 .then
,我们只需传入第二个参数即可。
错误处理
直接在 .catch
处理即可。
可以在 .catch
后继续使用 then
,这样可以实现错误恢复的逻辑。
错误传递
在 Promise 中错误的传递遵循以下规则:
- 向下传递:错误会沿着 Promise 链向下传递,直到遇到第一个
catch
处理器。在错误被处理之前的所有 then
都会被跳过。
- 错误恢复:
catch
处理器可以返回一个值来恢复正常流程,返回的值会传递给下一个 then
处理器。
- 错误再抛出:
catch
处理器也可以抛出新的错误,新错误会继续向下传递直到遇到下一个 catch
。
- 平行处理:同一个 Promise 可以有多个并行的处理链,每个链都会独立处理错误。
- finally 处理:
finally
处理器总是会执行,不管是否发生错误,finally
不能改变 Promise 链的值或错误状态。
基本的错误传递
多个 catch
复杂的错误处理链
then(null, errorHandler) 和 catch(errorHandler) 的区别
前面提到了捕获错误时可以通过 .then(null, errorHandler)
,实际上,catch(errorHandler)
是 then(null, errorHandler)
的语法糖。
他们的区别是 then(null, errorHandler)
只能捕获上一个 Promise 的错误,而 catch(errorHandler)
可以捕获链中之前所有未处理的错误。
throw new Error 和 reject 的区别
基本区别
最终处理
在 Promise 结束时执行,无论 Promise 是成功(fulfilled)还是失败(rejected),Promise 都会执行 finally
方法。
finally 的特点是:回调函数不接收任何参数、返回的值会被忽略,不会改变 Promise 链的结果、抛出的错误会传播到后续的 catch、会等待内部的 Promise 完成。
Promise 上的方法
Promise.all()
Promise.all() 是一个用于处理多个Promise的静态方法,它接收一个Promise数组作为参数,并返回一个新的Promise。这个新Promise会等待所有的Promise都完成(或第一个失败)。
只有 promise123 的状态都变成 fulfilled,promise 的状态才会变成 fulfilled,此时 promise123 的返回值组成一个数组,传递给 promise 的回调函数。
只要 promise123 之中有一个被 rejected ,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。
一些特殊情况:
-
传入空数组
-
传入非 Promise 值
-
传入的 Promise 有 catch 方法
如果作为参数的 Promise 实例,自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all() 的 catch 方法。
如果在自定义的 catch 方法中又抛出新的错误,且没有再次捕获,这个新错误会传播到 Promise.all() 的 catch 方法。
Promise.race()
Promise.race() 是一个静态方法,它接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新 Promise 会采用第一个完成(无论是成功还是失败)的 Promise 的状态和结果。
“race” 意味着竞赛,哪个 Promise 先完成就返回哪个结果。
一些特殊情况:
-
传入空数组
-
传入非 Promise 值
Promise.allSettled()
Promise.allSettled() 是一个静态方法,它接收一个 Promise 数组作为参数,并返回一个新的 Promise。这个方法会等待所有 Promise 完成(无论是成功还是失败),并返回一个包含每个 Promise 结果的数组。
每个 Promise 的结果对象包含两个属性:
- 成功的情况:
{ status: "fulfilled", value: 结果值 }
- 失败的情况:
{ status: "rejected", reason: 错误原因 }
Promise.resolve()
Promise.resolve() 是 Promise 的一个静态方法,它返回一个解析过的(fulfilled)Promise 对象。这个方法可以将现有值转换为 Promise 对象,或者返回一个新的已解决的 Promise。
Promise.reject()
Promise.reject() 是 Promise 的静态方法,它返回一个被拒绝(rejected)状态的 Promise 对象。与 Promise.resolve() 相反,它用于创建一个立即失败的 Promise。
实现 Promise
Promise 带来的问题
当 Promise 代码的调用层数过多时也会造成代码可读性下降。
async/await
async/await 是 ES2017 (ES8) 引入的语法糖,主要用于解决 Promise 中的回调地狱和代码可读性问题,使异步代码看起来更像同步代码。
例如,将上面的 fetchUserData
转换成 async 函数后代码可读性更高。
async
表示这个函数返回一个 promise。
await
让 JavaScript 引擎等待直到 promise 完成并返回结果。await
实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。
相关资料