不啰嗦,先来一个最基本的:
// v1.0.0
class PPromise {
constructor(exector) {
const self = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
function resolve(value) {
// 因为不是箭头函数,所以稍微注意一下this的指向问题
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
}
}
function reject(reason) {
// 一样注意一下this的指向问题
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
}
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
// 有可能想不到这里要判断一下是否是函数(毕竟一开始学到的就是then的参数是两个函数)
typeof onFulfilled === 'function' && onFulfilled(this.value);
} else if (this.state === 'rejected') {
typeof onRejected === 'function' && onRejected(this.reason);
}
}
}
上面这些就已经实现了一个最基本的Promise,可以用最基本的例子试一下:
const p = new PPromise((resolve, reject) => {
resolve(12345); // 或reject(67890);
});
p.then(res => {
console.log(res);
}, err => {
console.error(err);
});
继续升级。之所以说上面的是最基本的,是因为只要加入一个小小的延时,就不好使了:
const p = new PPromise((resolve, reject) => {
setTimeout(() => {
resolve(12345);
}, 500);
});
p.then(res => {
// 执行到此处时,state还是pending, 500ms之后才会变为fulfilled,所以这里不会打印
console.log(res);
}, err => {
console.error(err);
})
可以联想到,要解决这个问题,就要引入发布-订阅模式了:
// v1.0.1
class PPromise {
constructor(exector) {
const self = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = []; // 增加成功队列
this.onRejectedCallbacks = []; // 增加失败队列
function resolve(value) {
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
// 发布,轮询成功队列
self.onResolvedCallbacks.forEach(fn => fn(value));
}
}
function reject(reason) {
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
// 发布,轮询失败队列
self.onRejectedCallbacks.forEach(fn => fn(reason));
}
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
typeof onFulfilled === 'function' && onFulfilled(this.value);
} else if (this.state === 'rejected') {
typeof onRejected === 'function' && onRejected(this.reason);
} else {
// 成功订阅
typeof onFulfilled === 'function' && this.onResolvedCallbacks.push(onFulfilled);
// 失败订阅
typeof onRejected === 'function' && this.onRejectedCallbacks.push(onRejected);
}
}
}
接着升级。我们都知道then函数是异步任务(微任务),即:
console.log('1');
const p = new Promise((resolve, reject) => {
console.log('2');
resolve('result');
});
console.log('3');
p.then(res => {
console.log('5');
console.log(res);
});
console.log('4');
// 结果为1 -> 2 -> 3 -> 4 -> 5 -> result
很明显,v1.0.1中,then函数是同步任务,若按照上面的测试用例执行,结果会是1 -> 2 -> 3 -> 5 -> result -> 4。所以,要将then函数的执行放到异步中(一个延时为0的setTimeout):
// v1.0.2
class PPromise {
constructor(exector) {
const self = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
// 发布,轮询成功队列
self.onResolvedCallbacks.forEach(fn => fn());
}
}
function reject(reason) {
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
// 发布,轮询失败队列
self.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
setTimeout(() => {
typeof onFulfilled === 'function' && onFulfilled(this.value);
}, 0); // 当然,延时为0的setTimeout的真实含义不在本文的讨论范围中
} else if (this.state === 'rejected') {
setTimeout(() => {
typeof onRejected === 'function' && onRejected(this.reason);
}, 0);
} else {
// 成功订阅
typeof onFulfilled === 'function' && this.onResolvedCallbacks.push(() => {
setTimeout(() => {
onFulfilled(this.value);
}, 0);
});
// 失败订阅
typeof onRejected === 'function' && this.onRejectedCallbacks.push(() => {
setTimeout(() => {
onRejected(this.reason)
}, 0);
});
}
}
}
尽管then函数是异步任务(微任务),但为什么不能用一个setTimeout将then函数中的所有内容包起来,而非得在每个if条件中分别用 setTimeout包裹?其实如果只考虑fulfilled和 rejected状态的 state,的确用一个setTimeout包裹 then函数中的内容就好,但实际上,onFulfilled和onRejected的执行,在state的各个状态下都是异步的,看下面这个例子:
// 真实的Promise
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(); // 尽管先执行resolve
console.log(1);
}, 500)
});
p.then(res => {
console.log(2);
});
// 最终结果,500ms后先打印1再打印2
所以,为保证onFulfilled和onRejected一定异步执行,向onResolvedCallbacks(onRejectedCallbacks)注入的函数,也得是异步函数。
至此,一个50行左右的简单Promise就完成了,下面的升级就要考虑支持链式调用了。
首先,既然支持链式调用,那么then函数肯定不能像上面一样什么也不返回,而是一定要返回了一个新的PPromise,像下面这样:
then(onFulfilled, onRejected) {
// return一个新的PPromise
const p2 = new PPromise((resolve, reject) => {
// ......
});
return p2;
}
参照promise/A+规范,then函数的规则是这样的:
- 若onFulfilled(onRejected)返回一个值x,则执行p2的resolve,x作为参数;
- 若onFulfilled(onRejected)抛出异常e,则执行p2的reject,e作为参数;
- 若onFulfilled不是函数且初始promise的resolve执行,执行p2的resolve(有参数则传递参数);
- 若onRejected不是函数且初始promise的reject执行,执行p2的reject(有参数则传递参数); 后两点或许不太好想象(再一次,毕竟我们一开始学到的就是then的参数是两个函数,顶多是undefined),其实就是下面这样:
// 这样
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1122334)
}, 1000);
});
p.then(1).then(r => {
console.log(r); // 这里打印1122334
});
// 或者这样
const p = new Promise((resolve, reject) => {
setTimeout(() => {
reject(5566778)
}, 1000);
});
p.then(1, 2).then(null, e => {
console.error(e); // 这里打印5566778
});;
既然onFulfilled(onRejected)执行的结果有多种可能, 甚至有可能onFulfilled(onRejected)根本不是函数,那么是不是可以这样处理:
- 如果onFulfilled(onRejected)不是函数,那么它是什么其实并不重要(例如上面代码第7行的onFulfilled是数字1,难不成第8行要打印数字1?),所以可以将onFulfilled(onRejected)改为一个函数,不必复杂,简单的返回value(reason)即可;
- onFulfilled(onRejected)的执行结果有多种可能,那么可以记录一下结果,然后交由另一个函数统一处理。
- 既然onFulfilled(onRejected)的执行结果有多种可能,当然也有可能在执行过程中出错,所以最好将其包在try {}catch{}中。 如下:
then(onFulfilled, onRejected) {
// 不是函数就改为一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// return一个新的PPromise
const p2 = new PPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// “另一个函数”命名为handlePromise
// 既然是统一处理函数,当然resolve和reject也要作为参数传递
handlePromise(x, resolve, reject);
} catch (err) {
// 已经出错,当然也就没必要再进行处理,直接reject就好
reject(err);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
// 它是一个统一处理函数,onFulfilled和onRejected的返回结果都由它处理
handlePromise(x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
} else {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
handlePromise(x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
handlePromise(x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return p2;
}
function handlePromise(x, resolve, reject) {
// ......
}
下面分两种可能来讨论:
- 若x为原始类型数据或undefined,不必过多处理,直接resolve即可;
- 若x为对象或函数,为防止对象上或函数的原型上有then属性(属性值是当然是函数),需要其他处理(其实就是对thenable对象的处理)。 关于第一点可能会有疑惑,为什么一定是resolve(x),若x是onRejected的执行结果,也是resolve(x)而不是reject(x)吗?这就跟promise/A+规范相关了,无论x来自onFulfilled还是onRejected,x都是正确执行的结果(要分清“正确”和“非期望”,执行onRejected并不代表出错,只是当发生非我们期望的情况时,我们一般用onRejected处理),reject只负责处理执行出错的情况。 如下:
function handlePromise(x, resolve, reject) {
// 谁让typeof null也是'object'呢,当然要排除一下
if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
const then = x.then;
if (typeof then === 'function') {
// 对then的某种形式的调用
} else {
// 若then不是函数(或x上根本没有then属性),那x对于我们而言就是一个普通的数据,直接resolve就好
resolve(x);
}
} else {
// 即便第(上)一个then执行的是onRejected,下一个then执行的也是onFulfilled
// 大家可以用真实的Promise试验一下
resolve(x);
};
}
可以联想到,既然是thenable对象,那么若then是函数,对其的调用形式就如对一般对then函数的调用形式一样,同时既然是调用函数,就一定有执行出错的风险,所以也要用try{}catch{}包一下:
function handlePromise(x, resolve, reject) {
if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
try {
const then = x.then;
if (typeof then === 'function') {
// 防止有this的指向问题,借助一下call
then.call(x, r => {
resolve(r);
}, e => {
reject(e);
})
} else {
resolve(x);
}
} catch (e) {
// 当然直接reject就好
reject(e);
}
} else {
resolve(x);
};
}
-----------------------华丽的分割线----------------------
这里添加一条分割线,目的是什么最后再说。
接着升级。上面代码第8行,r也有可能又是一个Promise,那么就又得取出then,执行then.call(......),然后又有可能得到一个Promise......既然有这样的可能,那么应该很容易联想到,我们在第8行不应该简单的resolve,而应该执行递归,这样就可一劳永逸:
// 不写那么多了,就是上面的第8行改为:
handlePromise(r, resolve, reject);
另外还有一种极端的情况,若x是p2,那么就会造成死循环、无法结束的情况,就像下面这样:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(12345);
}, 500);
});
const p1 = p.then(res => {
return p1; // 自己等待自己完成,出错
});
所以在handlePromise的一开始,判断一下x是否和外层的then返回的PPromise相等,若相等直接reject即可,当然,这样的话就得给handlePromise增加一个参数了,即外层的then返回的PPromise:
// 多一个参数
function handlePromise(x, resolve, reject, newPPromise) {
if (x === newPPromise) {
// 抛一个TypeError
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x, r => {
handlePromise(r, resolve, reject, newPPromise); // 这里也多一个
}, e => {
reject(e);
})
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
} else {
resolve(x);
};
}
// 当然,then函数中四处对handlePromise的调用也要多传一个参数
不知会不会好奇,为什么上面代码第9行,也要包在try{}里,难道从一个对象上获取一个数据还能出错?就算没有该属性也能得到一个undefined不是么?其实,这就是典型的防范“小人”了,看看下面这个例子,是不是就能理解为什么了:
const x = {};
Object.defineProperty(x, 'then', {
get() {
throw new Error('就是坑你,咋地');
}
});
继续。我们都知道Promise的resolve和reject只能执行一个,不能两个都执行,thenable对象的then函数也不例外,即下面这样是不允许的:
const p = new Promise((resolve, reject) => {
resolve('result');
});
p.then((r) => {
return {
then(resolve, reject) {
resolve(r);
reject(r); // 这里不会执行
}
};
})
所以在handlePromise还要增加判断机制,判断若已执行过一个,就不再执行另一个了:
function handlePromise(x, resolve, reject, newPPromise) {
if (x === newPPromise) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
let done = false; // 增加done变量
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
r => {
if (done) {
return; // 已执行过,就不执行了
}
done = true;
handlePromise(r, resolve, reject, newPPromise);
},
e => {
if (done) {
return;
}
done = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (done) {
return;
}
done = true;
reject(e);
}
} else {
resolve(x);
}
}
到这里,一个初版的Promise就完成了,附一下完整代码:
// v1.0.3
class PPromise {
constructor(exector) {
const self = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
self.onResolvedCallbacks.forEach(fn => fn());
}
}
function reject(reason) {
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const p2 = new PPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
handlePromise(x, resolve, reject, p2);
} catch (err) {
reject(err);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
handlePromise(x, resolve, reject, p2);
} catch (err) {
reject(err);
}
}, 0);
} else {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
handlePromise(x, resolve, reject, p2);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
handlePromise(x, resolve, reject, p2);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return p2;
}
}
function handlePromise(x, resolve, reject, newPPromise) {
if (x === newPPromise) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
let done = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
r => {
if (done) {
return;
}
done = true;
handlePromise(r, resolve, reject, newPPromise);
},
e => {
if (done) {
return;
}
done = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (done) {
return;
}
done = true;
reject(e);
}
} else {
resolve(x);
}
}
如何测试正确性?node上有个promises-aplus-tests的插件,我们可以用它来测试:
// 1. npm i promises-aplus-tests
// 2. 新建js文件,像下面这样组织代码
const promisesAplusTests = require('promises-aplus-tests');
class PPromise {
// ......
}
function handlePromise(x, resolve, reject, newPPromise) {
// ......
}
PPromise.deferred = PPromise.defer = function () {
var dfd = {};
dfd.promise = new PPromise(function (fulfill, reject) {
dfd.resolve = fulfill;
dfd.reject = reject;
});
return dfd;
};
promisesAplusTests(PPromise, function (err) {});
// 3. 保存,命令行执行“node 文件名”
可以看到,通过了872项测试用例。
总结:
- 是否还记得那条“华丽的分割线”?分割线之后的对PPromise的修改(优化),包括两处“特别说明”,个人感觉都不是第一次写Promise就能写出来的,需要经过对Promise的长期使用、足够了解,甚至用promises-aplus-tests测试出错,根据错误信息才发觉的。毕竟根据一开始对Promise的学习,很可能我们没想过、甚至根本想不到“还有这种骚操作”。
- 当然,这里只实现了then函数,resolve/reject/race/all等api都还未实现,毕竟Promise也还在不断进化中(例如ES2020加入的allSettled,目前也并非所有浏览器都支持),后续慢慢补充。 补充一些Promise的api:
class PPromise {
// ......
// resolve/reject比较容易,就是返回一个fulfilled/rejected状态的Promise即可
static resolve(value) {
return new PPromise(resolve => {
resolve(value);
});
}
static reject(reason) {
return new PPromise((_, reject) => {
reject(reason);
});
}
// race也比较容易,可以联想到函数整体返回一个Promise,其中依次轮询Promise数组,有一个执行完成即整体执行完成
static race(list) {
// 当然,暂只考虑核心逻辑,其他非标准(异常)情况暂不考虑
return new PPromise((resolve, reject) => {
list.forEach(item => {
item.then(resolve, reject);
});
});
}
// all的基本套路与race相同,也是依次轮询Promise数组,稍微复杂之处在于需要知道all的特性
// 即“全部成功才成功,有一个失败就失败”,且结果数组的顺序和Promise数组的顺序一一对应
static all(list) {
// 同样,暂只考虑核心逻辑
return new PPromise((resolve, reject) => {
const out = [];
let len = 0;
list.forEach((item, index) => {
item.then(res => {
out[index] = res;
len += 1;
len === list.length && resolve(out);
}, reject);
});
});
}
}
共勉。