一、Promise 的设计
1.1 为什么需要 Promise?
在 JavaScript 中,回调函数(callback)是最常用。然而,随着异步场景复杂度的增加,传统回调方式暴露了如下问题:
- 回调地狱:
嵌套层级太深,导致代码难以维护。
示例:
const readFile = (fileName) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`读取文件: ${fileName}`);
if (fileName) resolve(`${fileName} 内容`);
else reject('文件不存在');
}, 1000);
});
};
// 执行多个任务,每个任务依赖于上一个任务的结果
readFile('file1')
.then((data1) => {
console.log('处理:', data1);
return readFile('file2').then((data2) => {
console.log('处理:', data2);
return readFile('file3').then((data3) => {
console.log('处理:', data3);
});
});
})
.catch((err) => {
console.error('出错了:', err);
});
- 错误处理困难:
每个回调函数需要单独处理错误逻辑,且无法统一捕获。 - 缺乏组合能力:
同时执行多个异步操作,并等待它们的结果,回调难以解决。
为了解决这些问题,Promise 通过一套状态管理机制,将异步操作抽象为对象,提供链式调用、统一错误处理以及任务组合等能力。
1.2 Promise 的三大设计原则
- 状态管理:
Promise 有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已失败)。状态一旦从pending转变为fulfilled或rejected,就不可逆。 - 链式调用:
then和catch方法返回新的 Promise,支持任务按顺序组织。 - 基于事件循环:
Promise 的回调通过微任务队列执行,保证异步代码更加精确。
二、Promise 与事件循环
2.1 JavaScript 的事件循环
要理解 Promise 的执行顺序,首先要理解 JavaScript 的事件循环机制。事件循环主要分为以下两类任务:
- 宏任务(MacroTask):
包括主线程代码、setTimeout、setInterval、I/O 操作等。 - 微任务(MicroTask):
包括Promise.then、queueMicrotask和MutationObserver。
执行顺序:
- 每次事件循环会从 宏任务队列 中取出一个任务执行,随后执行完所有的 微任务。
- 如果微任务队列清空后,主线程仍然空闲,则继续处理下一个宏任务。
2.2 Promise 在事件循环中的角色
Promise 的 .then 和 .catch 注册的回调函数会被加入 微任务队列,优先级高于宏任务。微任务的调度确保了 Promise 的回调在主线程任务结束后立即执行。
示例 1:Promise 和定时器的执行顺序
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
Promise.resolve().then(() => {
console.log('C');
});
console.log('D');
执行顺序:
A
D
C
B
分析:
console.log('A')和console.log('D')是主线程代码,优先执行。setTimeout的回调进入宏任务队列。Promise.then的回调进入微任务队列。- 主线程任务完成后,先执行微任务(
C),再执行宏任务(B)。
示例 2:嵌套微任务与宏任务
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => {
console.log('3');
});
}, 0);
Promise.resolve().then(() => {
console.log('4');
setTimeout(() => {
console.log('5');
}, 0);
});
console.log('6');
执行顺序:
1
6
4
2
3
5
执行过程分析:
- 主线程执行:打印
1和6。 setTimeout回调进入宏任务队列。Promise.resolve().then(() => console.log('4'))回调进入微任务队列。- 主线程执行完毕后,清空微任务队列,执行
4。 - 微任务执行中创建的宏任务(
setTimeout的console.log('5'))进入宏任务队列。 - 执行第一个宏任务,打印
2,同时产生一个新的微任务(console.log('3'))。 - 清空新产生的微任务队列,打印
3。 - 执行最后一个宏任务,打印
5。
三、Promise 的实现原理
想深入理解 Promise 的工作机制,还要从其核心实现。
以下是一个符合 Promise/A+ 规范 的简化实现:
3.1 状态管理与回调存储
Promise 的状态管理机制确保其状态不可逆,且每个状态都维护独立的回调队列。
实现:
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态
this.value = undefined; // 终值或拒因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (err) {
reject(err);
}
} else if (this.state === 'rejected') {
try {
const result = onRejected(this.value);
resolve(result);
} catch (err) {
reject(err);
}
} else {
this.onFulfilledCallbacks.push(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const result = onRejected(this.value);
resolve(result);
} catch (err) {
reject(err);
}
});
}
});
}
}
3.2 链式调用的实现细节
链式调用通过 then 方法返回一个新的 Promise 对象实现。返回的 Promise 的状态由 then 回调的执行结果决定:
- 如果返回值是普通值,则包装为
Promise.resolve(value)。 - 如果返回值是一个 Promise,则等待其状态完成后再执行下一步。
- 如果抛出错误,则返回的 Promise 状态为
rejected。
四、Promise 的性能优化与实际应用
4.1 并发任务的处理
通过 Promise.all 并行执行多个异步任务,并同时获取结果:
const fetchData = (url) => new Promise((resolve) => setTimeout(() => resolve(url), 1000));
Promise.all([fetchData('/api/user'), fetchData('/api/posts')])
.then(([user, posts]) => {
console.log('用户信息:', user);
console.log('帖子信息:', posts);
});
4.2 超时控制
为异步任务设置超时时间:
const withTimeout = (promise, timeout) => {
return Promise.race([
promise,
new Promise((_, reject) => setTimeout(() => reject('超时'), timeout)),
]);
};
五、Promise 类方法
5.1、Promise.resolve
功能
Promise.resolve 将一个值(或非 Promise)包装成一个 已完成的 Promise。
如果传入的值是一个 Promise,则直接返回该 Promise。
语法
Promise.resolve(value);
参数
value: 任意值。如果是一个 Promise,则原样返回;如果是普通值,则包装为一个 Fulfilled 状态的 Promise。
使用场景
- 快速将一个值转化为 Promise,统一处理异步和同步任务。
- 用于测试和调试。
示例
// 传入普通值
Promise.resolve(42).then((value) => console.log(value)); // 输出: 42
// 传入 Promise
const promise = Promise.resolve(Promise.resolve('Hello'));
promise.then((value) => console.log(value)); // 输出: Hello
5.2、Promise.reject
功能
Promise.reject 返回一个 已失败的 Promise,并携带指定的错误原因。
语法
Promise.reject(reason);
参数
reason: Promise 被拒绝的原因(通常是一个错误对象)。
使用场景
- 手动创建一个 Rejected 状态的 Promise。
- 模拟错误场景以进行测试。
示例
Promise.reject('Error!')
.catch((reason) => console.error(reason)); // 输出: Error!
5.3、Promise.all
功能
Promise.all 接受一个包含多个 Promise 的数组,并返回一个新的 Promise。
- 成功条件:所有 Promise 都 Fulfilled,返回一个包含每个 Promise 结果的数组。
- 失败条件:只要有一个 Promise 被 Rejected,则返回该 Promise 的拒因。
语法
Promise.all(iterable);
参数
iterable: 一个可以迭代的对象(如数组、Set),每一项为一个 Promise。
使用场景
- 并行执行多个异步任务,并等待所有任务完成。
- 确保依赖关系中所有任务都成功后再继续后续操作。
示例
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((results) => console.log(results)) // 输出: [1, 2, 3]
.catch((err) => console.error(err)); // 只要有一个失败,就进入 catch
注意
如果数组中有非 Promise 值,Promise.all 会将其视为 Fulfilled 状态的 Promise:
Promise.all([1, Promise.resolve(2)]).then(console.log); // 输出: [1, 2]
5.4、Promise.allSettled
功能
Promise.allSettled 返回一个新的 Promise,永远成功。
- 它会等待所有的 Promise 都完成(无论成功还是失败),返回每个 Promise 的结果对象数组。
语法
Promise.allSettled(iterable);
参数
iterable: 可迭代对象,每一项为一个 Promise。
使用场景
- 批量处理异步任务,并需要查看每个任务的状态,而不是中断整个流程。
- 不希望因为一个任务失败而影响其他任务。
示例
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error!');
const promise3 = Promise.resolve(3);
Promise.allSettled([promise1, promise2, promise3]).then((results) =>
console.log(results)
);
/* 输出:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Error!' },
{ status: 'fulfilled', value: 3 }
]
*/
5.5、Promise.race
功能
Promise.race 接收一个包含多个 Promise 的数组,返回第一个**完成(无论是 Fulfilled 还是 Rejected)**的 Promise。
语法
Promise.race(iterable);
参数
iterable: 可迭代对象,每一项为一个 Promise。
使用场景
- 超时控制:希望异步任务在一定时间内完成,否则返回超时错误。
- 从多个任务中选择最快完成的。
示例
const promise1 = new Promise((resolve) => setTimeout(() => resolve('A'), 100));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('B'), 200));
Promise.race([promise1, promise2]).then((value) => console.log(value)); // 输出: A
超时控制示例
const task = new Promise((resolve) => setTimeout(() => resolve('Done'), 500));
const timeout = new Promise((_, reject) => setTimeout(() => reject('Timeout!'), 300));
Promise.race([task, timeout])
.then(console.log)
.catch(console.error); // 输出: Timeout!
5.6、Promise.any
功能
Promise.any 返回第一个 成功(Fulfilled) 的 Promise。
- 如果所有 Promise 都被 Rejected,则返回一个 Rejected 状态的 Promise,并包含所有拒因的
AggregateError。
语法
Promise.any(iterable);
参数
iterable: 可迭代对象,每一项为一个 Promise。
使用场景
- 在多个任务中选择最快完成且成功的任务。
- 希望忽略失败的任务,只处理成功结果。
示例
const promise1 = Promise.reject('Error!');
const promise2 = Promise.resolve('Success!');
const promise3 = Promise.resolve('Another success!');
Promise.any([promise1, promise2, promise3]).then((value) =>
console.log(value)
);
// 输出: Success!
错误示例
const promise1 = Promise.reject('Error1');
const promise2 = Promise.reject('Error2');
Promise.any([promise1, promise2]).catch((error) => console.log(error));
// 输出: AggregateError: All promises were rejected
5.7、总结
| 方法 | 功能概述 | 成功返回 | 失败返回 |
|---|---|---|---|
Promise.resolve | 创建一个 Fulfilled 状态的 Promise | value | - |
Promise.reject | 创建一个 Rejected 状态的 Promise | - | reason |
Promise.all | 所有 Promise 成功时返回结果数组,任意一个失败则直接返回该失败的 Promise | 结果数组 | 第一个失败的 Promise |
Promise.allSettled | 等待所有 Promise 完成,无论成功或失败 | 包含状态和值/原因的结果对象数组 | 永远不会失败 |
Promise.race | 返回第一个完成(成功或失败)的 Promise | 第一个完成的结果 | 第一个完成的错误 |
Promise.any | 返回第一个成功的 Promise,所有失败时返回 AggregateError | 第一个成功的结果 | AggregateError(包含所有失败的原因) |