面试官:请你解释一下Promise工作原理
今天我们继续讲promise,本文主要包含对promise中resolve,reject和.then函数的介绍,最后通过对上述函数的理解手写出Promise源码。
Promise
当创建一个新的Promise
实例时,构造函数会立即执行一个传递给它的executor
函数,该函数接受两个参数:resolve
和reject
。这两个参数实际上是函数,用于改变Promise
的状态。resolve
用于在异步操作成功完成时调用,而reject
则在异步操作失败时调用。
- 拥有三种状态为 pending fulfilled rejected
- 实例化一个 promise 会得到一个状态为 pending 的实例对象
- 该对象可以访问 promise 原型上的 then 方法
- 当该对象中的状态没有变更为 fulfilled,then 接收到的回调是不触发的
resolve
resolve函数的作用
resolve
函数被用来解析一个Promise
,使其从pending
状态变为fulfilled
(已解决)状态。
示例:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a is ok');
resolve()//如果我们不写resolve,.then里的逻辑是不生效的
}, 1000)
})
}
function b() {
console.log('b is ok');
}
a()
.then(
() => {
b()
})
如果不加
resolve()
,
运行结果不会运行b()
:
当resolve
被调用时,它还可以接受一个参数,这个参数会被传递给.then()
方法中第一个回调函数作为其参数
new Promise((resolve, reject) => {
// 异步操作完成后
resolve(1); // 传递一个值1给resolve
});
.then(
(res) => {
console.log(res);
b()
})
.then()
方法的使用
.then()
方法允许你注册回调函数,这些函数会在Promise
变为fulfilled
状态时执行。如果你在resolve
中传递了一个值,那么这个值会被作为.then()
方法的第一个回调函数的参数。
例如:
new Promise((resolve, reject) => {
resolve(1); // 传递一个值1给resolve
})
.then((value) => {
console.log(value); // 输出1
});
如果没有调用resolve
,Promise
将保持在pending
状态,因此任何.then()
方法中的回调都不会被执行。
- 如果
resolve
中带了值,那么.then()
中的第一个回调函数将接收这个值作为参数。
.then()里的第二个回调完全等同于.catch()
在Promise
的.then()
方法中,可以传递两个回调函数:第一个是当Promise
成功(即resolved
)时调用的,第二个则是当Promise
失败(即rejected
)时调用的。
但是,从ES6开始,为了更清晰地区分成功和失败的处理逻辑,推荐使用.then()
来处理成功的情况,以及单独的.catch()
来处理失败的情况。
然而,在语法上,.then()
的第二个参数确实可以用来处理rejected
状态,这与.catch()
是等效的。也就是说,下面两段代码是等价的:
使用.then()
的第二个参数:
a()
.then(
(res) => {
console.log(res, 'res');
},
(err) => {
console.log(err, "err");
}
);
使用
.catch()
:
a()
.then((res) => {
console.log(res, 'res');
})
.catch((err) => {
console.log(err, "err");
});
在两种情况下,如果Promise
被reject
,则err
将会被打印。
但是,使用.catch()
使得错误处理更加明显和易于理解,尤其是在链式调用中,它能够明确地表明这是处理错误的地方。
此外,.catch()
还可以捕获前面.then()
中抛出的异常,而.then()
的第二个参数则不能。
例如:
a()
.then(() => {
throw new Error('An error occurred');
})
.catch((err) => {
console.log(err.message);
});
在这种情况下,.catch()
将捕获在.then()
内部抛出的异常。而如果使用.then()
的第二个参数,它将无法捕获前面.then()
中抛出的异常,除非你再次调用.then()
并提供一个新的失败回调。
因此,尽管.then()
的第二个参数在功能上与.catch()
相似,但.catch()
提供了更好的可读性和更全面的错误处理能力。
reject
调用reject
函数时,会使Promise
进入rejected
(拒绝)状态。
一旦Promise
处于rejected
状态,任何随后的.then()
方法将不会被调用,因为它们只处理fulfilled
(已解决)的情况
我们可以使用.catch()
方法来处理rejected
状态。
.catch()
实际上是一个简化的.then(null, onRejected)
调用,它只关注rejected
状态,并且会接收reject
函数传入的任何值或错误作为参数。
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a is ok');
reject(2) // 这里使用reject
}, 1000)
})
}
function b() {
console.log('b is ok');
}
a()
.then(
(res) => {
console.log(res);
b()
})
.catch(err => {
console.log(err); // 这里会捕获到reject抛出的值2
})
因为Promise
通过reject(2)
进入了rejected
状态,所以.then()
块中的代码不会执行,而.catch()
中的代码会被调用来处理错误。
如果在Promise
被拒绝后调用.then()
而不调用.catch()
,那么任何未处理的rejected
状态都会导致程序中未捕获的异常,这通常不是我们想要的结果。
因此,确保在可能的rejected
情况下使用.catch()
来处理错误,或者在.then()
之后链式调用.catch()
来捕捉任何可能的异常。
总结:
- 必须调用
resolve
或reject
来改变Promise
的状态。resolve
用于标记异步操作的成功,使Promise
变为fulfilled
状态。reject
用于标记异步操作的失败,使Promise
变为rejected
状态。.then()
用于处理fulfilled
状态,而.catch()
用于处理rejected
状态。
手写Promise
思路
- 创建Promise:当使用
new Promise()
构造函数时,必须传递一个执行器函数executor
,这个函数会被立即调用。- executor的参数:
executor
函数接受两个参数:resolve
和reject
。这两个函数由Promise
构造函数提供,用于改变Promise
的状态。- 状态转换:
Promise
有三种状态:pending
(待定)、fulfilled
(已解决)和rejected
(已拒绝)。状态只能从pending
变成fulfilled
或rejected
,且一旦状态改变,就不能再变回pending
或改变为另一个状态。- 状态不变性:一旦
Promise
变为fulfilled
或rejected
,其状态就永久固定,不会再改变。- then方法:每个
Promise
都有then
方法,它接受两个可选的回调函数:onFulfilled
和onRejected
,分别用于处理Promise
成功和失败的情况。- then方法的执行:如果在调用
then
时Promise
已经处于fulfilled
或rejected
状态,那么相应的回调onFulfilled
或onRejected
将立即执行。如果Promise
仍处于pending
状态,那么这些回调将被存储起来,直到状态确定后才执行。- 可选的回调:
then
方法的onFulfilled
和onRejected
参数是可选的,如果省略,then
方法将假定一个默认的行为(对于onFulfilled
,默认为返回值本身;对于onRejected
,默认为抛出错误)。- 链式调用:
Promise
的then
方法可以被多次调用,每次调用都返回一个新的Promise
,这使得你可以链接多个then
方法来构建复杂的异步操作流程。- 传递结果:如果
then
方法的回调返回一个值,这个值将作为下一个then
方法中onFulfilled
回调的参数。- 传递错误:如果
then
方法的回调抛出一个异常,这个异常将作为下一个then
方法中onRejected
回调的参数。- 处理返回的Promise:如果
then
方法的回调返回另一个Promise
,那么整个链式调用会等待这个新Promise
的结果。如果新Promise
成功,链式调用继续向下执行;如果新Promise
失败,链式调用会跳转到最近的onRejected
回调。
完整代码:
class MyPromise {
constructor(executor) {
// 初始化状态和值
this.status = 'pending'; // 当前Promise的状态:pending, fulfilled, 或 rejected
this.value = null; // fulfilled时的值
this.reason = null; // rejected时的原因
this.onFulfilledCallbacks = []; // 存储所有成功回调
this.onRejectedCallbacks = []; // 存储所有失败回调
// 解析Promise,改变其状态到fulfilled
const resolve = value => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// 拒绝Promise,改变其状态到rejected
const reject = reason => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// 尝试执行executor,如果抛出错误,自动调用reject
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// then方法,用于链式调用和处理结果
then(onFulfilled, onRejected) {
// 默认处理,如果回调函数不存在,则直接返回值或抛出原因
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// 返回新的Promise,允许链式调用
return new MyPromise((resolve, reject) => {
// 根据当前状态,决定执行哪个回调
if (this.status === 'fulfilled') {
setTimeout(() => {
try {
// 调用onFulfilled并处理其结果
const x = onFulfilled(this.value);
this.resolvePromise(x, resolve, reject);
} catch (error) {
// 如果onFulfilled抛出错误,直接拒绝新的Promise
reject(error);
}
}, 0);
} else if (this.status === 'rejected') {
setTimeout(() => {
try {
// 调用onRejected并处理其结果
const x = onRejected(this.reason);
this.resolvePromise(x, resolve, reject);
} catch (error) {
// 如果onRejected抛出错误,直接拒绝新的Promise
reject(error);
}
}, 0);
} else {
// 如果状态是pending,将回调存储起来,等待状态改变
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
}
// 辅助函数,用于处理then方法中返回的值x
resolvePromise(promise2, x, resolve, reject) {
// 检查循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle'));
}
// 检查x的类型
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called = false; // 标记x.then是否已经被调用
try {
let then = x.then;
if (typeof then === 'function') {
// 如果x是一个Promise,调用其then方法
then.call(x, y => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r);
});
} else {
// 如果x不是一个Promise,直接解析
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
// 捕获x.then抛出的错误
if (called) return;
called = true;
reject(error);
}
} else {
// 如果x不是对象或函数,直接解析
resolve(x);
}
}
}
module.exports = MyPromise;
以上就是本文的全部内容,通过手写Promise,我们不仅加深了对Promise内部机制的理解,还掌握了如何实现自己的Promise库,这对于进一步学习异步编程和深入JavaScript核心概念有着重要的意义。希望本文对你有所帮助,感谢你的阅读!
转载自:https://juejin.cn/post/7393307982653014054