在前端面试和日常的前端开发中,我们会一直用到Promise。在面试中,我被要求手写Promise,手写Promise.all(),实现一个限制并发Promise最大数目的函数,还有很多其他关于Promise的顺序问题。在前端开发中,我们使用Promsie来获取数据并确保我们的代码按照正确顺序运行。
在面试中被问到Promises时,我总是会害怕。我知道怎么使用Promises和一些基本的规则,但如果事情变得花里胡哨,我就开始会担心我会答错。我在网上搜索并且阅读了几个关于Promise的实现,这些实现极其复杂,而且我看不出为什么其中的代码能够实现某些特定的功能。所以我决定写这篇文章,实现一个简单版的Promise,一步一步地去实现,希望你能从这次书写Promise的旅程中受益。
这并不是严格按照Promise A+标准实现的Promise,而是我为了更好地理解Promise而做的小小练习。如果你对于Promise是什么以及它们是如何使用的感到毫无头绪,我建议你花几分钟阅读MDN对Promise的解释
让我们开始吧!在开头,我们会学习如何创造和消费Promise,然后我们会实现基础版的Promise,并逐步完善,使我们的Promise可以支持异步和链式调用。
怎么创建出一个Promise?
Promise确保我们的代码以正确的顺序运行,创建Promise其实只有一种方式,但用起来却有两种。
1.创建并立刻消费
运行以下代码会创建并立刻消费Promise。
// promise is triggered to resolve here, when creating
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolved');
}, 1000);
});
promise.then((res) => {
console.log(res);
});
注意:setTimeOut模拟的是耗时的异步操作,类似于调用后端这种。
2.写一个函数返回一个Promise,只有函数调用,Promise才会resolve
const getPromise = function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res);
}, 1000);
});
};
// promise is triggered to resolve here, when calling the function
getPromise().then((res) => {
console.log(res);
});
这看起来很简单,却是我第一个恍然大悟的时刻。一个新创建出的Promise会立刻执行。如果我们不想Promise立刻执行,我们可以把它包在一个函数里,什么时候想执行,就调用函数。
现在我们知道我们要做的第一件事是什么:我们需要写一个构造函数(constructor),构造函数接收有两个参数的函数,一个参数是resolve,另一个参数是reject,这两个参数被用来确定Promise的状态。
怎么消费一个Promise?
Promise被创建出来后可以被消费,有三种主要的方式来消费Promise:then(),catch(),和finally()。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res);
}, 1000);
});
promise.then((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
promise.catch((err) => {
console.log(err);
});
promise.finally(() => {
console.log("finally over");
});
.then(res => onFulfilled(res), err => onRejected(err))接收两个由消费者定义的函数,这两个函数在Promise被解析或被拒绝后调用。
.catch(err => onRejected(err))接收一个函数,在Promise被拒绝后调用。
.finally(() => onSettled())不论Promise是被解析还是被拒绝, onSettled都会被调用。finally在catch和then之后调用, onSettled不接任何参数。
在这篇文章中,我们只实现then方法,因为其他两个和then很相似。
现在,让我们看看我们需要做什么
Promise有以下特性:
constructor((resolve, reject) => {})handler有两个参数resolve和reject。.then(res => onFulfilled(res), err => onRejected(err))有两个参数,onFulfilled和onRejected,在Promise被解析或被拒绝后,Promise的消费者会调用这两个函数之一。- Promise的状态:等待,兑现和拒绝。
- Promise的value:结果
result会被传给onFulfilled,错误error会被传给onRejected。
Promise的基础版本
当创建一个Promise的时候,构造器接收一个处理器函数handler,handler执行,在Promise完成的时候调用resolve,在Promise出错的时候调用reject。因此,在Promise类中,构造器需要创建被传给handler的resolve和reject。
我们根据Promise的状态调用回调函数,关于Promise的状态有两条规则:
-
所有的Promise开始的时候都是
pending状态。 -
一旦Promise的状态被更改为
fulfilled或rejected,之后状态无法再更改。 这是我们的第一版Promise,如果你看见任何明显的bug,请先不要在意,我们会在之后解决这些bug。
// version 1
class Promise {
constructor(handler) {
this.status = "pending";
this.value = null;
const resolve = value => {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = value;
}
};
const reject = value => {
if (this.status === "pending") {
this.status = "rejected";
this.value = value;
}
};
try {
handler(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === "fulfilled") {
onFulfilled(this.value);
} else if (this.status === "rejected") {
onRejected(this.value);
}
}
}
// testing code
const p1 = new Promise((resolve, reject) => {
resolve('resolved!');
});
const p2 = new Promise((resolve, reject) => {
reject('rejected!')
})
p1.then((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
p2.then((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
// 'p1 resolved!'
// 'p2 rejected!'
这是一个很好的开始,但是你可能会注意到有个明显的bug。这一版不支持异步调用,但Promise的主要用途正在于此。如果我们把测试代码改成如下并且使用setTimeout来兑现Promise,代码并没有输出。
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved!'), 1000);
});
p3.then((res) => {
console.log(res);
},(err) => {
console.log(err);
});
// no console log output, our Promise didn't work
//没有输出,我们的Promise不起作用。
这是因为在我们调用.then的时候,我们的Promise的状态是pending,onFulfilled和onRejected都没有被调用。我们需要支持异步操作!
Promise的升级版-支持异步操作
为了支持异步,我们需要把onFulfilled和onRejected存起来,一旦Promise的状态改变,我们就立刻执行这些函数。
同一个Promise可以调用.then()多次,因此我们会用到两个数组onFulfilledCallbacks 和onRejectedCallbacks来存储函数并在Promise兑现或拒绝的时候立刻调用这些函数。
以下是我们的第二版:
class Promise {
constructor(handler) {
this.status = "pending";
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn(value));
}
};
const reject = value => {
if (this.status === "pending") {
this.status = "rejected";
this.value = value;
this.onRejectedCallbacks.forEach(fn => fn(value));
}
};
try {
handler(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === "pending") {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
if (this.status === "fulfilled") {
onFulfilled(this.value);
}
if (this.status === "rejected") {
onRejected(this.value);
}
}
}
// testing code
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved!'), 1000);
});
p3.then((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
// ' resolved!'
我们现在有了很大的进步!我觉得如果如果你能在面试中写出这个版本,你的面试官会很满意。但是我们可以实现链式调用来让他们更加刮目相看。
更好的Promise -实现链式调用
我们知道可以像下面这样对Promise进行链式调用。
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved first one'), 1000);
});
p.then((res) => {
console.log(res);
return res + ' do some calculation';
}).then(res => {
console.log(res);
});
// 'resolved first one'
我们的第二版Promise在这样链式调用的时候会打印出resolved first one并且抛错--Uncaught TypeError: Cannot read property ‘then’ of undefined,这是因为我们的then方法没有返回任何值。
让我们把.then()修改一下,.then()应该返回一个promise,onFulfilled和onRejected的返回值用来确定这个promise的状态。
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.status === "pending") {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
resolve(fulfilledFromLastPromise);
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
reject(rejectedFromLastPromise);
} catch (err) {
reject(err);
}
});
}
if (this.status === "fulfilled") {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
resolve(fulfilledFromLastPromise);
} catch (err) {
reject(err);
}
}
if (this.status === "rejected") {
try {
const rejectedFromLastPromise = onRejected(this.value);
reject(rejectedFromLastPromise);
} catch (err) {
reject(err);
}
}
});
}
现在如果我们用同样的代码测试我们的promise,两个console.log输出都有了!这太好了!但我们还没有完全完成。要是onFulfilled和onRejected的返回值是一个promise怎么办?现实中的使用场景是:我从一个服务器中fetch了一些数据,在我得到response后,我需要使用response中的数据来从另一个服务器中fetch额外的数据。
我们现在的Promise无法正确运行以下代码:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved first one'), 1000);
})
p1.then((res) => {
console.log(res);
return new Promise(resolve => {
setTimeout(() => resolve('resolved second one'), 1000);
});
}).then(res => {
console.log(res);
});
// ideally, it should
// 1 sec later, log 'resolved first one'
// 1 sec later, log 'resolved second one'
// 理想情况下
// 1秒钟后,输出'resolved first one'
// 一秒钟后,输出'resolved second one'
因为如果OnFulfilled和onRejected的返回值是Promise,我们的Promise不知道如何处理。
如何处理这种情况?调用.then()
好吧,我的头开始痛了...坚持住!我保证这已经到最后了。
让我们整理出我们现在有的三个Promise:
- "第一个
Promise" -- 最初的Promise,也就是上面代码中的p1。 - "第二个
Promise" -- 由then()返回的Promise。 - "第三个
Promise" -- 由onFulfilled/Onrejected返回的Promise。
在.then()里,我们创造了一个新的Promise(也就是第二个Promise),如果onFulfilled返回一个Promise(也就是第三个Promise),我们会调用这个Promise的.then(resolve,reject),并且我们把第二个Promise的resolve和reject传入,这样在第三个Promise确定状态的时候第二个Promise也会确定状态。
下面是改进后的.then():
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.status === "pending") {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
});
}
if (this.status === "fulfilled") {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
}
if (this.status === "rejected") {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
}
});
}
我们终于完成了,让我们看看我们代码的最终版本!
最终版本
class Promise {
constructor(handler) {
this.status = "pending";
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.status === "pending") {
this.status = "fulfilled";
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn(value));
}
};
const reject = value => {
if (this.status === "pending") {
this.status = "rejected";
this.value = value;
this.onRejectedCallbacks.forEach(fn => fn(value));
}
};
try {
handler(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
if (this.status === "pending") {
this.onFulfilledCallbacks.push(() => {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
});
this.onRejectedCallbacks.push(() => {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
});
}
if (this.status === "fulfilled") {
try {
const fulfilledFromLastPromise = onFulfilled(this.value);
if (fulfilledFromLastPromise instanceof Promise) {
fulfilledFromLastPromise.then(resolve, reject);
} else {
resolve(fulfilledFromLastPromise);
}
} catch (err) {
reject(err);
}
}
if (this.status === "rejected") {
try {
const rejectedFromLastPromise = onRejected(this.value);
if (rejectedFromLastPromise instanceof Promise) {
rejectedFromLastPromise.then(resolve, reject);
} else {
reject(rejectedFromLastPromise);
}
} catch (err) {
reject(err);
}
}
});
}
}
// testing code
let p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved first one'), 1000);
});
p1.then((res) => {
console.log(res);
return new Promise(resolve => {
setTimeout(() => resolve('resolved second one'), 1000);
});
}).then(res => {
console.log(res);
});
// 1 sec later, 'resolved first one'
// 1 sec later, 'resolved second one'
耶!我们终于完成了!
祝贺你!你做到了!我们从Promise的基础实现开始,一步步到最后把该有的功能都完善,希望这个过程对你有帮助。我们谈到了如何使用Promise,如何消费Promise,并且实现了异步和链式调用。
译者注语
我在网上搜索如何实现Promise的时候发现了这篇文章,对我理解Promise有很大的帮助,文章非常清晰易懂,而且配有例子,所有空暇时间翻译了这篇文章,希望能帮助大家更好地理解Promise。原文链接po在下方。