这是一个简版非规范的Promise实现,其中前置知识点比较重要,理解了前置知识点的内容,也就了解了Promise最基本的原理。
前置知识
这小节的核心是:把函数名当变量用。
以下三点理解之后,就能够实现简单的Promise了。
- 类型为Function的变量,可以像函数一样调用。
举个栗子
let myFn = function () {};
复制代码
你可以直接调用
myFn();
复制代码
和下面的代码效果是一样的
function myFn() {
// code here...
}
myFn();
复制代码
- 函数数组,即函数里的每一个元素都是函数,可以用遍历调用。
let fnArray = [];
let fn1 = () => { console.log(1) };
let fn2 = () => { console.log(2) };
fnArray.push(fn1);
fnArray.push(fn2);
fnArray.forEach(cb => cb());
复制代码
这段代码的运行结果是
1
2
复制代码
- 函数可以当作参数传递
function showYouFn(fn1, fn2) {
// 调用
fn1();
fn2();
}
let myFn1 = () => { console.log(3); };
let myFn2 = () => { console.log(4); };
showYouFn(myFn1, myFn2);
复制代码
回想一下:把函数名当变量用。
实现
下面是一个简陋得不像Promise的Promise实现
function MyPromise(fn) {
function resolve(value) {
console.log(value);
}
function reject(value) {
console.log(value);
}
fn(resolve, reject);
}
复制代码
现在你就可以用上自定义的Promise了
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
});
复制代码
将会在2秒后输出
你看到我啦!
复制代码
下面的代码效果和上面的一样,写出来是为了再次强调:函数当变量用。
let promiseFn = (resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
};
new MyPromise(promiseFn);
复制代码
解释一下整体代码:
MyPromise
中的参数 fn
是需要用户传入的自定义函数(该函数需要接收两个参数)。
MyPromise
中的内部还有两个函数 resolve
和 reject
,其中 resolve
表示用户的异步任务成功时应该调用的函数,reject
表示用户的异步任务失败时该调用的函数。
那用户如何调用 resolve
和 reject
呢?
很简单,把两个函数当作 fn
的参数传递出去即可。
所以 MyPromise
内部在调用 fn
时会把 resolve
和 reject
当作参数传递给 fn
。
然后用户在自定义函数内调用 resolve
或 reject
来通知 MyPromise
异步任务已经执行完了。
建议复制下面的代码去控制台试试
function MyPromise(fn) {
function resolve(value) {
console.log(value);
}
function reject(value) {
console.log(value);
}
fn(resolve, reject);
}
new MyPromise((resolve, reject) => {
setTimeout(()=>{
resolve('你看到我啦!');
}, 2000);
});
复制代码
通过上面的代码可以发现一个问题,我们不知道Promise的异步任务进行到哪一步了、是成功还是失败了。
所以增加三个状态用来标识一个Promise的异步任务进行到何种程度了。
pending
、resolved
、rejected
分别表示 执行中、已完成、已失败。
然后通过观察用户调用的是 resolve
还是 reject
可以判断当前Promise的状态。
那么会有三种情况:
- 在用户调用
resolve
或reject
之前状态是pending
- 用户调用
resolve
时,状态将变为resolved
- 用户调用
reject
时,状态将变为rejected
下面进行代码的改造,定义了三个常量表示状态以及一个变量 state
用来存储当前状态。
并且当 resolve
被调用时将 state
修改为 resolved
。
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
// 初始时状态为执行中,即pending
that.state = PENDING;
function resolve(value) {
console.log(value);
// resolve被调用时修改状态
that.state = RESOLVED;
}
function reject(value) {
console.log(value);
// reject被调用时修改状态
that.state = REJECTED;
}
fn(resolve, reject);
}
复制代码
OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromise
的 resolve
被调用,那也只是MyPromise
知道任务完成了,用户还不知道呢。
所以我们需要回调函数告诉用户,是的,其实就是回调函数。
这时候就轮到 then
方法出场了,用户通过then
方法传入回调函数, MyPromise
将在成功调用 resolve
时调用用户传入的回调函数。
开始改造代码,MyPromise
内部需要变量存储回调函数,then
方法的作用就是将用户传入的回调函数赋予 MyPromise
内的变量。
所以 then
方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数。
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 两个存储回调函数的变量
that.resolvedCallback;
that.rejectedCallback;
function resolve(value) {
console.log(value);
that.state = RESOLVED;
// 调用用户的回调函数,并告知结果
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(value) {
console.log(value);
that.state = REJECTED;
// 调用用户的回调函数,并告知结果
that.rejectedCallback && that.rejectedCallback(value);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallback = onFulfilled;
that.rejectedCallback = onRejected;
}
复制代码
是的,一个简版Promise几乎大功告成,让我们再试试在浏览器执行如下代码(注意我删除了 resolve
和 reject
里的console语句)
(function () {
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 两个存储回调函数的变量
that.resolvedCallback;
that.rejectedCallback;
function resolve(value) {
that.state = RESOLVED;
// 调用用户的回调函数,并告知结果
that.resolvedCallback && that.resolvedCallback(value);
}
function reject(value) {
that.state = REJECTED;
// 调用用户的回调函数,并告知结果
that.rejectedCallback && that.rejectedCallback(value);
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallback = onFulfilled;
that.rejectedCallback = onRejected;
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('你又看到我啦~');
}, 2000);
}).then(value => {
console.log(value);
});
})();
复制代码
上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then
方法呢?
是的,只有最后一个 then
方法里的回调函数能执行,这当然没法满足我们的需要。
于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。
改造后的代码如下:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 注意这里变成了两个数组
that.resolvedCallbackArray = [];
that.rejectedCallbackArray = [];
function resolve(value) {
that.state = RESOLVED;
// 遍历用户的回调函数,告知结果
that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
}
function reject(value) {
that.state = REJECTED;
// 遍历用户的回调函数,告知结果
that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallbackArray.push(onFulfilled);
that.rejectedCallbackArray.push(onRejected);
// 这里是为了链式调用,即myPromise.then().then().then()...
return that;
}
复制代码
现在我们可以多次调用 then
方法了。试试在控制台运行下面的代码:
(function () {
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
const that = this;
that.state = PENDING;
// 注意这里变成了两个数组
that.resolvedCallbackArray = [];
that.rejectedCallbackArray = [];
function resolve(value) {
that.state = RESOLVED;
// 遍历用户的回调函数,告知结果
that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
}
function reject(value) {
that.state = REJECTED;
// 遍历用户的回调函数,告知结果
that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
}
fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
that.resolvedCallbackArray.push(onFulfilled);
that.rejectedCallbackArray.push(onRejected);
return that;
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('你又看到我啦~');
}, 2000)
}).then(value => {
console.log('第一次', value);
}).then(value => {
console.log('第二次', value);
});
})();
复制代码
上面已经是简版Promise的实现了。
但是我们还可以更完善一点,增强 MyPromise
的健壮性。
例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...
语句,并在发生错误时主动执行reject
函数告知用户。
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
复制代码
又或者,对参数进行校验,状态进行判断等,以 then
为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejected
或resolved
,此时调用then
呢?
改造 then
后代码如下:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
if(typeof onRejected !== 'function') {
onRejected = v => v;
}
if(typeof onFulfilled !== 'function') {
onFulfilled = v => { throw r };
}
const that = this;
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
复制代码
还有其他方法在理解了基本原理之后可以自己根据规范改造,本文到此告一段落,感谢阅读。