Promise
何为Promise
- Promise的英文解释意为承诺,它在JS中可以用来存储一个值,并确保在你需要将这个值返回。这一点听上去似乎也并没有什么新奇的地方,任何一个对象都可以存储值,为什么非要使用Promise呢?Promise的特殊之处在于它提供了一种更优化的方式来处理异步操作。在传统的JavaScript编程中,处理异步操作(如网络请求或读取文件)通常会使用回调函数。然而,回调函数的嵌套和管理往往会导致代码可读性和可维护性的问题,即所谓的"回调地狱"。而Promise通过引入一套规范和语法,使得异步操作的处理更加简洁、清晰和可组合。
创建一个Promise
- 在创建一个Promise时,我们需要传递一个构造器,它通常是一个回调函数。构造器接受两个参数,
resolve和reject,它们分别是用于成功和失败时的回调函数。
const promise = new Promise((resolve,reject)=>{
// 在这里编写异步操作的代码
// 当异步操作成功完成时,调用resolve函数,并传递结果
// 当异步操作失败时,调用reject函数,并传递错误信息
})
- 当我们调用
resolve函数传递一个结果时,该Promise的状态会由pending转为fulfilled - 当我们调用
reject函数传递一个结果时,该Promise的状态会由pending转为rejected
Promise三种状态
在Promise中有一个表示状态的属性,它记录Promise执行的状态
pendin(进行中):该状态意味着操作仍在进行中,同时也是promise实例的初始状态。fulfilled(成功):该状态意味着操作已经被接受、操作成功完成。当操作完成时promise实例的then()方法会被调用。rejected(失败):该状态意味着操作失败,当操作失败时promise实例的catch()方法会被调用。
Promise实例方法
-
then():该方法用于指定当Promise状态变为已完成(fulfilled)时和当Promise状态变为已拒绝(rejected)时的回调函数。它接受两个参数,第一个参数是当Promise状态变为已完成时执行的回调函数,第二个参数是当Promise状态变为已拒绝时执行的回调函数。这两个参数都是可选的。 -
catch():该方法用于指定当Promise状态变为已拒绝时的回调函数。它接受一个参数,即当Promise状态变为已拒绝时执行的回调函数。它相当于调用then(null, onRejected),用于捕获Promise链中的错误。 -
finally():该方法用于指定无论Promise状态最终变为已完成还是已拒绝时都会执行的回调函数。它接受一个参数,即无论Promise状态最终如何都会执行的回调函数。通常用于清理资源或执行一些收尾的操作。
Promise静态方法
- Promise.resolve() 创建一个立即完成的Promise
- Promise.reject() 创建一个立即拒绝的Promise
- Promise.all([...]) 同时返回多个Promise的执行结果,其中有一个拒绝,就返回拒绝
- Promise.allSettled([...]) 同时返回多个Promise的执行结果(无论成功或失败)
- Promise.race([...]) 多个promise同时执行,谁快就返回谁的结果(无论成功或失败)
- Promise.any([...]) 多个promise同时执行,谁先返回成功结果就是谁的结果(如何全为失败则为失败)
Promise的执行原理
- Promise在执行,then就相当于给Promise的回调函数
- 当Promise的状态从pending变为fulfilled时,
then的回调函数会被放入到任务队列(微任务队列)中
- 当Promise的状态从pending变为fulfilled时,
- Promise在机制依赖于事件循环机制
事件循环机制
- JS是单线程的,它运行时基于事件循环机制(event loop)
- 调用栈
- 栈是一种后进先出的数据结构
- 调用栈放的是要执行的代码
- 任务队列(消息队列)
- 队列
- 队列是一种先进先出的数据结构
- 当调用栈中的代码执行完毕后,队列中的代码才会按照顺序依次进入栈中执行
- 在JS中有两种主要的任务队列
- 宏任务队列
大部分事件任务都会去宏任务队列排队 - 微任务队列
Promise中的回调函数(then,catch,finally)
queueMicrotask() 用来向微任务队列中添加一个任务
- 宏任务队列
- 队列
- 整个流程
- 先执行调用栈中的代码
- 执行微任务队列中的所有任务
- 执行宏任务队列中的所有任务
- 调用栈
下面是MyPromise实现的一个逐步分解和重写,旨在提高可读性和理解性。我会尽量保持每一步的逻辑清晰,并解释每部分的作用。
尝试手写基础的Promise对象
定义基础结构
首先,我们定义MyPromise类的基本结构,包括状态、结果以及回调数组。
class MyPromise {
constructor(executor) {
this.#state = 'pending'; // 初始状态为pending
this.#result = undefined; // 结果初始未定义
this.#callbacks = []; // 回调数组初始化为空
// 立即执行executor函数,传递resolve和reject方法
try {
executor(this.#resolve.bind(this), this.#reject.bind(this));
} catch (error) {
// 如果executor直接抛出错误,则认为promise被reject
this.#reject(error);
}
}
#resolve = (value) => { /* 实现待定 */ };
#reject = (reason) => { /* 实现待定 */ };
// then方法待实现
then(onFulfilled, onRejected) { /* 实现待定 */ }
}
实现resolve和reject方法
接下来,实现#resolve和#reject方法,处理状态变更及回调执行逻辑。
#resolve(value) {
if (this.#state !== 'pending') return; // 非pending状态直接返回
this.#state = 'fulfilled'; // 修改状态为fulfilled
this.#result = value; // 保存结果
// 微任务队列中执行所有成功回调
queueMicrotask(() => {
this.#callbacks.filter(cb => cb.onFulfilled).forEach(cb => cb.onFulfilled(value));
});
}
#reject(reason) {
if (this.#state !== 'pending') return; // 非pending状态直接返回
this.#state = 'rejected'; // 修改状态为rejected
this.#result = reason; // 保存原因
// 微任务队列中执行所有失败回调
queueMicrotask(() => {
this.#callbacks.filter(cb => cb.onRejected).forEach(cb => cb.onRejected(reason));
});
}
改进then方法以处理回调和链式调用
最后,完善then方法,确保它可以正确处理回调函数,并返回一个新的Promise以便链式调用。
then(onFulfilled, onRejected) {
// 默认处理函数,避免undefined情况
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// 创建一个新的Promise来处理链式调用
let nextPromise = new MyPromise((resolve, reject) => {
// 根据当前状态立即或延迟执行回调
if (this.#state === 'fulfilled') {
queueMicrotask(() => {
try {
let result = onFulfilled(this.#result);
resolve(result); // 解析下一个Promise
} catch (error) {
reject(error); // 如果onFulfilled抛出错误,则reject下一个Promise
}
});
} else if (this.#state === 'rejected') {
queueMicrotask(() => {
try {
let result = onRejected(this.#result);
resolve(result);
} catch (error) {
reject(error);
}
});
} else { // pending状态
// 存储回调,等待状态改变时执行
this.#callbacks.push({
onFulfilled: value => {
try {
let result = onFulfilled(value);
resolve(result);
} catch (error) {
reject(error);
}
},
onRejected: reason => {
try {
let result = onRejected(reason);
resolve(result);
} catch (error) {
reject(error);
}
}
});
}
});
return nextPromise;
}