最近在面试中遇到了很多关于Promise的问题,因为以前的业务在请求方面并不复杂,多数时候都是在用async/await,对Promise的理解还是有所欠缺,最近重新学习了一下Promise,尽量避免写成API式的文章,主要还是结合自己的一些理解和思考来整理一下。
为什么要使用 Promise
众所周知,JavaScript 的主线程是单线程执行的,所有的同步代码都是在一个线程中执行的,当遇到一些耗时操作时(比如网络请求、文件读取等),如果采用同步的方式去处理这些操作,就会阻塞主线程,导致页面卡顿,用户体验变差。为了解决这个问题,我们发明了异步编程,最早的异步编程方式是回调函数(Callback),我们先看一个简单的例子:
/* by 01022.hk - online tools website : 01022.hk/zh/formatcss.html */ function add(getX, getY, finalCallback) { var x, y; getX(function (xVal) { x = xVal; if (y !== undefined) { finalCallback(x + y); } }); getY(function (yVal) { y = yVal; if (x !== undefined) { finalCallback(x + y); } }); } function fetchX(xCallback) { setTimeout(function () { xCallback(2); }, 1000); } function fetchY(yCallback) { setTimeout(function () { yCallback(3); }, 1000); } add(fetchX, fetchY, function (sum) { console.log("Sum is: " + sum); });fetchX和fetchY是两个异步函数,分别模拟从服务器获取数据的过程,我们要进行x+y的计算,如果它们中的任何一个还没有准备好,就等待两者都准备好。我们逐步拆解这个过程:
调用
add函数,传入fetchX、fetchY和回调函数。在
add函数内部,调用getX(即fetchX),传入一个回调函数。
/* by 01022.hk - online tools website : 01022.hk/zh/formatcss.html */ function (xVal) { x = xVal; if (y !== undefined) { finalCallback(x + y); } }fetchX开始执行,经过1秒钟后,调用传入的回调函数(xCallback),将2作为参数传递进去。回调函数执行,
x被赋值为2,然后检查y是否已经准备好(即y是否不为undefined)。此时y还没有准备好,所以不会调用最终的finalCallback。同样的过程发生在
getY(即fetchY)上,经过1秒钟后,y被赋值为3,然后检查x是否已经准备好。此时x已经准备好了(x=2),所以调用finalCallback,计算出最终的结果5,并打印出来。
从这个例子中,我们是否能看出使用回调函数来处理异步操作存在一些问题?首先,也许这个思路很巧妙,但是代码很复杂,我在逐步拆解前很难直接理解这个过程。其次,如果有更多的异步操作需要处理,代码会变得更加复杂,难以维护,这就是著名的“回调地狱”问题。
回想我刚上班时,使用的还是 jQuery,jQuery 的 Ajax 请求就是基于回调函数的,代码如下:
$.ajax({ url: "https://api.example.com/data", method: "GET", success: function (data) { console.log("Data received:", data); $.ajax({ url: "https://api.example.com/more-data", method: "GET", success: function (moreData) { console.log("More data received:", moreData); // 继续嵌套更多的回调... }, error: function (err) { console.error("Error fetching more data:", err); }, }); }, error: function (err) { console.error("Error fetching data:", err); }, });显然,随着嵌套层级的增加,代码变得越来越难以阅读和维护,而且错误处理也变得复杂。所以回收这一节的标题,因为用回调函数来处理异步操作确实存在一些问题:
- 可读性差:嵌套的回调函数使代码难以理解。
- 错误处理复杂:每个回调函数都需要单独处理错误,导致代码冗长。
- 控制流困难:管理多个异步操作的顺序和依赖关系变得复杂。
等讲完Promise之后我们看下Promise是否能解决这些问题。
Promise
是什么
通俗的说,我们可以把Promise理解成一个异步操作的代理,它是异步操作的返回值,原本只有同步操作才能有返回值,异步操作只能使用我们上面所说的回调函数嵌套来获得结果。
异步方法不会立即返回最终值,而是返回一个
Promise,以便在将来的某个时间点提供该值。
Promise的基本用法应该都很熟悉了,我们创建一个Promise的例子:
// ES6 原生 Promise const asyncTask = new Promise((resolve, reject) => { // 模拟异步操作(比如接口请求、文件读取) setTimeout(() => { const success = true; if (success) { resolve("操作成功"); // 成功回调 } else { reject("操作失败"); // 失败回调 } }, 1000); }); // 调用 Promise asyncTask .then((result) => console.log(result)) // 输出:操作成功 .catch((error) => console.log(error)) .finally(() => console.log("操作完成"));可以看到,我们把异步操作setTimeout包装在Promise中,然后通过then、catch和finally来处理结果和错误,setTimeout可以是任意异步操作,比如网络请求、文件读取等。
隐藏在这些 API 之下的还有一个参数,一个Promise必然处于以下三种状态之一:
Pending(进行中):初始状态,既不是成功,也不是失败。Fulfilled(已成功):操作成功完成。Rejected(已失败):操作失败。
这一部分内容可以参考 MDN 的 Promise - JavaScript | MDN,讲得很清楚。
参考这张图,Pending状态通向两个结果:Fulfilled和Rejected,这个过程是单向不可逆的,一旦状态改变,就会永久保持该状态。当任意一种情况发生时,then方法注册的回调函数就会被调用,即不再处于"待定"(Pending)状态,称之为"已敲定"(Settled)。
Rejected
我们先看Rejected的情况:
// catch const failedTask = new Promise((resolve, reject) => { setTimeout(() => { reject("操作失败"); // 失败回调 }, 1000); }); failedTask .then((result) => console.log(result)) .catch((error) => console.log(error)) // 输出:操作失败 .finally(() => console.log("操作完成")); // then 第二个参数 const anotherFailedTask = new Promise((resolve, reject) => { setTimeout(() => { reject("操作失败"); // 失败回调 }, 1000); }); anotherFailedTask .then( (result) => console.log("成功:" + result), (error) => console.log("失败:" + error), ) // 输出:操作失败 .finally(() => console.log("操作完成"));有两种方式可以捕获Promise的拒绝状态:一种是使用catch方法,另一种是将错误处理函数作为then方法的第二个参数传入。两种方式都能有效地处理Promise的拒绝状态,如果不进行错误处理,未捕获的拒绝会导致未处理的Promise拒绝警告。更详细的说明我们后面再聊,这里只看用法。
Fulfilled
在构造器Promise(..)中,我们通常用两个回调函数来表示成功和失败的情况,这两个函数的命名并不固定,通常我们使用resolve和reject,reject很清楚地表示失败,并且代表Promise进入Rejected状态,而成功的回调函数resolve(决议),它表示Promise进入Fulfilled状态,这里用 ES6 规范中的回调命名来说明:
myPromise.then((result) => onFulfilled, onRejected)链式调用
在提到Promise时,链式调用是一个非常重要的概念,上面的例子中,我们看到Promise对象可以调用then方法,而then方法又可以调用catch和finally方法,因为then方法返回的仍然是一个Promise对象,而catch和finally方法内在内部调用的也是then方法,这样它们就可以链式调用。
// Promise.resolve这种写法我们之后讨论 Promise.resolve('第一步结果') .then(res => { console.log(res); // 打印:第一步结果 // return 普通值 → 新 Promise 状态为 fulfilled return '第二步结果'; }) .then(res => { console.log(res); // 打印:第二步结果 // return 新 Promise → 新 Promise 跟随该 Promise 的状态 return Promise.resolve('第三步结果'); }) .then(res => { console.log(res); // 打印:第三步结果 });通过例子可以看到,链式调用可以将多个异步操作串联起来,每个then方法处理上一个Promise的结果,这就解决了我们最开始提到的回调地狱问题,使代码更加清晰和易于维护。
这个例子中还有一个细节,在then方法中通过return来传递值,当使用return返回一个普通值时,新的Promise会进入Fulfilled状态,也可以返回一个新的Promise对象,这样新的Promise会跟随该Promise的状态。值得注意的是,如果返回的是一个thenable对象(具有then方法的对象),Promise也会等待该对象解决,这使得Promise可以与其他实现了类Promise接口的库进行互操作。
大体上我们了解了Promise的用法,我们用Promise来实现嵌套异步操作:
function getFirstData() { // 返回一个 Promise,用 setTimeout 模拟异步 return new Promise((resolve) => { setTimeout(() => { const data = "第一个异步操作的结果"; console.log("Data received:", data); // 异步成功,传递结果给下一个 .then() resolve(data); }, 1000); }); } function getSecondData(prevData) { return new Promise((resolve) => { setTimeout(() => { const moreData = `第二个异步操作的结果(基于上一步:${prevData})`; console.log("More data received:", moreData); resolve(moreData); // 可选:继续传递结果给后续链式调用 }, 1000); }); } // 链式调用 getFirstData() .then((data) => { // 第一个异步成功后,执行第二个异步 return getSecondData(data); }) .catch((err) => { // 统一捕获所有异步操作的错误 console.error("异步操作出错:", err); });Promise 解决了什么问题
通过这个例子可以看到,我们一开始提出的回调函数的三个问题得到了不同程度的解决:
- 可读性提升:通过链式调用,代码结构更加清晰,每个异步操作都在自己的
then块中处理,避免了嵌套回调的复杂性。 - 统一错误处理:使用
catch方法可以统一捕获所有异步操作的错误,简化了错误处理逻辑。 - 控制流简化:通过链式调用,可以更容易地管理多个异步操作的顺序和依赖关系,使代码更易于理解。
这里有点像一种if语句的替代写法:
if (condition1) { // do something if (condition2) { // do something if (condition3) { // do something } } } // 可以改写为: if(!condition1) return; // do something if(!condition2) return; // do something if(!condition3) return; // do something换个思路,作用相同,但代码的可读性会变高,不过Promise要复杂得多,我没有直接使用一开始的回调函数版本来对比,并非做不到,而是涉及了新的知识点,需要用到Promise的一些 API,我打算换一种角度来理解,然后我们再回头看这个对比。
Promise 的 API 与原型
Promise是 ES6(ES2015)引入的一种用于处理异步操作的对象,最近刚写了一篇关于原型的文章:对于原型、原型链和继承的理解,这里就是想从原型和面向对象的角度来加深一下理解,我们还是用前面的例子,分步拆解:
// ES6 原生 Promise const asyncTask = new Promise((resolve, reject) => { // 模拟异步操作(比如接口请求、文件读取) setTimeout(() => { const success = true; if (success) { resolve("操作成功"); // 成功回调 } else { reject("操作失败"); // 失败回调 } }, 1000); }); // 调用 Promise asyncTask .then((result) => console.log(result)) // 输出:操作成功 .catch((error) => console.log(error)) .finally(() => console.log("操作完成"));构造函数 Promise()
先从核心语句说起,new Promise((resolve, reject) => { ... })这里事关两个概念:构造函数和new。
Promise()是一个构造器(Constructor)或者说构造函数,用于创建Promise对象。
使用构造函数的形式来创建对象有几个好处:
封装初始化逻辑:
Promise构造函数内部封装了初始化Promise对象所需的逻辑,比如设置初始状态(pending)、设置回调函数(resolve和reject)。共享方法:通过构造函数创建的对象实例可以共享原型上的方法(如
then、catch、finally),避免每个实例都创建一份相同的方法,节省内存。立即执行:当我们创建一个新的
Promise实例时,传入的执行器函数(executor function)会立即执行,这使得我们可以在创建Promise的同时开始异步操作。
而new关键字用于创建一个新的对象实例,并将其原型链接到构造函数的原型对象上,也就是让新创建的对象继承构造函数原型上的方法和属性,结合上面的例子就是说我们创建的asyncTask对象会继承Promise.prototype上的方法,比如then、catch和finally,这也就是为什么我们可以在asyncTask上调用这些方法,以及进行前面所说的链式调用。
要注意的一点是,执行器函数的返回值对Promise的影响有限,在then方法中我们通过return来传递值,但在执行器函数中return语句仅影响控制流程,并不会直接改变Promise的状态,Promise的状态只能通过调用resolve或reject来改变。
const myPromise = new Promise((resolve, reject) => { // 一些异步操作 if (/* 操作成功 */) { resolve("成功结果"); } else { reject("失败原因"); } return "这个返回值不会影响 Promise 的状态"; });入参 (resolve, reject) =>
接下来我们看构造函数的入参(resolve, reject) => { ... },也就是执行器函数(executor function),它会在Promise实例创建时立即执行,这个上面说过了。使用Promise时,我们不会关注执行器函数,主要是使用这个函数的入参resolve和reject用来改变Promise的状态。
function executor(resolveFunc, rejectFunc) { // 通常,`executor` 函数用于封装某些接受回调函数作为参数的异步操作,比如上面的 `setTimeout` 函数 }当调用resolve或reject时,Promise的状态会立即改变,从pending变为fulfilled或rejected,然后执行回调函数,这个回调函数就是我们通过then方法注册的函数。
const p = new Promise((resolve) => { console.log('1. 执行器函数立即执行'); resolve('成功'); console.log('2. resolve 调用完成(同步)'); }); console.log('3. Promise 创建完成'); p.then((value) => { console.log('5. then 回调执行:', value); }); console.log('4. then 方法调用完成'); // 输出顺序: // 1. 执行器函数立即执行 // 2. resolve 调用完成(同步) // 3. Promise 创建完成 // 4. then 方法调用完成 // 5. then 回调执行: 成功但我们用到Promise时主要还是用于异步任务,then方法是典型的微任务(microtask),如果then方法先执行,里面的回调函数会被放入微任务队列,等待当前宏任务执行完毕后再执行。
对于更细致的执行顺序,之前有写过一篇关于事件循环的文章,刚好是用Promise举例,可以参考:有关 JavaScript 事件循环的若干疑问探究。
Promise内部的大致逻辑是这样的:
// Promise 内部简化实现 class MyPromise { constructor(executor) { this.state = 'pending'; // 状态 this.value = undefined; // 结果值 this.onFulfilledCallbacks = []; // ← 存储 then 的成功回调 this.onRejectedCallbacks = []; // ← 存储 then 的失败回调 const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; // ← 关键:遍历回调队列,将所有回调加入微任务 this.onFulfilledCallbacks.forEach(callback => { queueMicrotask(() => callback(value)); }); } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.value = reason; this.onRejectedCallbacks.forEach(callback => { queueMicrotask(() => callback(reason)); }); } }; executor(resolve, reject); } then(onFulfilled, onRejected) { // 如果 Promise 还是 pending,就把回调存起来 if (this.state === 'pending') { this.onFulfilledCallbacks.push(onFulfilled); // ← 存储回调 this.onRejectedCallbacks.push(onRejected); } // 如果 Promise 已经 fulfilled,立即将回调加入微任务 else if (this.state === 'fulfilled') { queueMicrotask(() => onFulfilled(this.value)); } // 如果 Promise 已经 rejected else if (this.state === 'rejected') { queueMicrotask(() => onRejected(this.value)); } return new MyPromise(() => {}); // 简化,实际更复杂 } }所以then中的回调函数被执行的前提是resolve或reject被调用并且then方法也被调用,这也是Promise能处理异步操作的关键。
静态方法
简单提一下,静态方法是直接挂载在构造函数上的方法,而不是实例对象上,以前面的例子来说,asyncTask是Promise的一个实例对象,而Promise.all(..)和Promise.resolve(..)这种则是Promise构造函数的一个静态方法。
基于上面的简单例子,Promise大体上的用法我们已经了解了,但还有很多 API 没有涉及到,我们可以通过打印Promise的原型来查看:
console.log(Promise.prototype);我们可以看到then、catch和finally方法都在Promise.prototype上,这些方法是实例方法,意味着它们可以被任何Promise实例调用。
由于安全机制,直接打印Promise本身是看不到原生代码的,我们换一种方式,只需要得到静态方法名就行:
console.log(Object.getOwnPropertyNames(Promise)); // 输出:['length', 'name', 'prototype', 'all', 'allSettled', 'any', 'race', 'resolve', 'reject', 'withResolvers', 'try']输出结果中有的熟悉有的不熟悉,因为我之前对Promise仅停留在会用的层面,所以有些我甚至是第一次知道,但没关系,通过原型再对照 MDN 文档,逐个学习一下。length、name和prototype是函数对象的默认属性,我们主要关注其他的静态方法:
Promise.resolve(..)和Promise.reject(..)
这两个方法应该是最常见的了,上面的例子中也用到过,reject比较简单,返回一个拒绝状态的Promise对象,入参就是拒绝的原因:
const promiseReject = Promise.reject(new Error("失败原因")); promiseReject.catch((reason) => { console.log(reason.message); // Expected output: 失败原因 }); // 或者 function resolved(result) { console.log("Resolved"); } function rejected(result) { console.log("Rejected:", result); } const promiseReject2 = Promise.reject("失败原因"); promiseReject2.then(resolved, rejected);resolve方法则比较复杂一些,它有两种返回形式:1. 如果入参是一个普通值(非Promise对象),则返回一个以该值为结果的已解决(fulfilled)状态的Promise对象,这一点与reject对应;2. 如果入参是一个Promise对象,则返回该Promise对象本身。
// 入参是普通值 const promise1 = Promise.resolve(123); promise1.then((value) => { console.log(value); // Expected output: 123 }); // 入参是 Promise 对象 const originalPromise = new Promise((resolve) => { setTimeout(() => { resolve("原始 Promise 结果"); }, 1000); }); const promise2 = Promise.resolve(originalPromise); promise2.then((value) => { console.log(value); // Expected output: 原始 Promise 结果 });Promise.all(..)、Promise.race(..)、Promise.allSettled(..)和Promise.any(..)
这四个我觉得可以放一起介绍,它们都是用于处理多个Promise对象的静态方法,入参都是一个可迭代对象(通常是数组),包含多个Promise对象,返回一个新的Promise对象,其他的区别用一张表格来说明:
| 方法 | 描述 | 返回值 |
|---|---|---|
Promise.all(..) | 全成功才成功,一失败就失败。 | 成功时返回一个包含所有结果的数组,失败时返回第一个失败的原因。 |
Promise.allSettled(..) | 等所有完成,无论成败。 | 结果是一个包含每个Promise结果状态的数组。 |
Promise.any(..) | 一成功就成功,全失败才失败。 | 成功时返回第一个成功的结果,失败时返回一个AggregateError,包含所有失败的原因。 |
Promise.race(..) | 谁先完成(成败均可),就用谁的结果。 | 结果是第一个解决或拒绝的Promise的结果或原因。 |
关于Promise.all(..)的应用,我们最开始的回调函数就是一个很好的例子,我们可以用它来重写:
function fetchX() { return new Promise((resolve) => { setTimeout(() => { resolve(2); }, 1000); }); } function fetchY() { return new Promise((resolve) => { setTimeout(() => { resolve(3); }, 1000); }); } function add() { return Promise.all([fetchX(), fetchY()]).then(([x, y]) => x + y); } add().then((sum) => { console.log("Sum is: " + sum); // 输出:Sum is: 5 });如果有三个异步操作:
function fetchZ() { return new Promise((resolve) => { setTimeout(() => { resolve(4); }, 1000); }); } function addThree() { return Promise.all([fetchX(), fetchY(), fetchZ()]).then(([x, y, z]) => x + y + z); }MDN上的Promise.allSettled(..)例子:
Promise.allSettled([ Promise.resolve(33), new Promise((resolve) => setTimeout(() => resolve(66), 0)), 99, Promise.reject(new Error("一个错误")), ]).then((values) => console.log(values)); // [ // { status: 'fulfilled', value: 33 }, // { status: 'fulfilled', value: 66 }, // { status: 'fulfilled', value: 99 }, // { status: 'rejected', reason: Error: 一个错误 } // ]这里的返回值有些不同,是一个对象数组,每个对象表示对应Promise的状态和结果。
Promise.any(..)的返回值是第一个成功的结果,如果所有Promise都失败了,则返回一个AggregateError,它包含所有失败的原因:
const promiseA = Promise.reject("失败原因 A"); const promiseB = Promise.reject("失败原因 B"); Promise.any([promiseA, promiseB]) .then((value) => { console.log(value); }) .catch((error) => { console.log(error); }); // 输出:AggregateError: All promises were rejected与其他三个方法不同,Promise.race(..)返回的Promise状态的敲定总是异步的,前面的三种方法入参的Promise数组中有一个甚至多个是已经解决(fulfilled)或拒绝(rejected)的Promise对象(简单来说,和上面的大部分例子一样,我们传入一个确定的值而不是异步方法),那么Promise.all(..)、Promise.allSettled(..)和Promise.any(..)会立即返回结果,而Promise.race(..)的返回值则是异步的。
MDN 针对每个方法的返回值都有详细的说明,比如说Promise.all(..),如果传入的参数为空,则它的状态会立即变为已解决(fulfilled)另外两种返回状态则为异步兑现(asynchronously fulfilled)和异步拒绝(asynchronously rejected),而Promise.any(..)则是相反的,如果传入的参数为空,则它的状态会立即变为已拒绝(rejected),其他情况都是异步的。
向Promise.race(..)传入一个空的可迭代对象会导致返回的Promise永远处于挂起状态(pending),因为没有任何Promise可以兑现或拒绝。
const foreverPendingPromise = Promise.race([]); console.log(foreverPendingPromise); setTimeout(() => { console.log("堆栈现在为空"); console.log(foreverPendingPromise); }); // 按顺序打印: // Promise { <state>: "pending" } // 堆栈现在为空 // Promise { <state>: "pending" }Promise.race(..)的异步性有什么意义呢?假设我们有一个网络请求操作,我们希望在一定时间内获得响应,否则就放弃请求,这时我们可以使用Promise.race(..)来实现超时控制:
const data = Promise.race([ fetch("/api"), new Promise((resolve, reject) => { // 5 秒后拒绝 setTimeout(() => reject(new Error("请求超时")), 5000); }), ]) .then((res) => res.json()) .catch((err) => displayError(err));Promise.try()
Promise.try()静态方法接受一个任意类型的回调函数(无论其是同步或异步,返回结果或抛出异常),并将其结果封装成一个Promise。
这是一个截止到目前(2026年2月)仍在提案阶段的 API,在一些现代浏览器和 Node.js 最新版本中已经可以使用,作用类似于async函数,可以将同步代码和异步代码统一处理为Promise对象:
Promise.try(() => { // 这里可以是同步代码 const result = synchronousFunction(); return result; }) .then((value) => { console.log("同步结果:", value); }) .catch((error) => { console.error("错误:", error); }); // 也可以是异步代码 Promise.try(async () => { const result = await asynchronousFunction(); return result; }) .then((value) => { console.log("异步结果:", value); }) .catch((error) => { console.error("错误:", error); });Promise.withResolvers()
Promise.withResolvers()静态方法返回一个对象,其包含一个新的Promise对象和两个函数,用于解决或拒绝它,对应于传入给Promise()构造函数执行器的两个参数。
它完全等价于下面的代码:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; });它的作用是简化创建一个可控的Promise对象,我们可以在外部调用resolve和reject来改变Promise的状态:
const { promise, resolve, reject } = Promise.withResolvers(); // 模拟异步操作 setTimeout(() => { const success = true; if (success) { resolve("操作成功"); } else { reject("操作失败"); } }, 1000); promise .then((result) => console.log(result)) .catch((error) => console.log(error)) .finally(() => console.log("操作完成"));这个 API 的使用场景比较少见,目前我还不能完全理解它的作用,感兴趣可以到 MDN 上查看。
async/await 与 Promise
最开始就说到async/await了,我是先接触到async/await这种写法的,然后才了解到它是基于Promise的语法糖,个人理解来说,async/await让异步代码看起来更像同步代码,主要是提高代码的可读性和可维护性,就像Promise之于回调函数一样。
在使用上,async/await的争议集中在是否要使用try/catch来处理错误,我之前的处理方式是在请求的封装里使用try/catch来捕获错误,调用时正常使用async/await,其他地方处理异步操作还是直接使用Promise。以前其实没有太深入考虑过合理性的问题,在新公司看代码规范时发现他们有针对这个问题讨论过,才意识到这个问题的重要性。关于这个问题争议比较大,而且关于async/await完全可以单独写一篇,这篇主要还是针对Promise的学习记录,再写下去也有些超篇幅了,之后学习时应该还会再聊到。
缺陷
这个部分对于我来说还是有些超纲了,但也有参考资料,列一下《你不知道的JavaScript》中卷提到的几个缺陷,不过这些纸质书有一定的时代性,内容仅供参考:
- 顺序错误处理: 如果构建了一个没有错误处理函数的Promise链,链中后续的
then仍然会被执行,可能导致错误被忽略或处理不当。 - 单一值:
Promise只能处理单一值的传递,无法直接处理多个值或复杂的数据结构(可以传递封装的对象,但如果在链中的每一步都进行封装和解封,就有些笨重了)。 - 单决议:
Promise一旦被解决(fulfilled)或拒绝(rejected),其状态就不能再改变,无法重新解决或拒绝。 - 惯性: 时代性的体现,考虑当时的环境
Promise还未普及,现在应该可以忽略这一点了。 - 不可取消: 一旦创建,
Promise就会一直执行,无法取消正在进行的异步操作。这个也有些时代性了,现在有AbortController可以配合fetch来实现取消请求的功能。 - 性能: 相较于回调函数,
Promise在创建和管理状态方面有一定的性能开销,但个人认为这在通常的应用场景中影响不大。
总结
说实话动笔之前就是觉得应该写一篇关于Promise的,但开始写之后发现没什么方向,相关资料也是浩如烟海,写这篇耗费了非常多的时间,开始不断地深挖细节后感觉有无穷无尽的问题,好在现在通过 AI 至少可以把这些问题大致理清楚,"大致"理解说明还有很多内容没有涉及,之后在项目中应该会更加注意Promise的应用,然后把《你不知道的JavaScript》的相关内容看完结合一下应该还可以再水一篇。