0. 前言
4. 手写 Promise
4.1 Promise骨架实现
在前面的内容中,我们知道Promise构造器的用法为:
var p = new Promise( function(resolve, reject){
// resolve()用于决议/完成这个promise
// reject()用于拒绝这个promise
})
除此之外,Promise对象一定会有一个.then()
方法。从这些信息中,我们可以得到一个基本的Promise对象,应该有这几个要素:
function Promise(excutor){
}
Promise.prototype.then = function(onfulfilled, onrejected) {
}
我们需要往这个骨架里面添加代码。我们知道excutor
是一个函数,它包含两个函数类型的参数resolve
和reject
。我们还知道Promise有三种状态:pending、fulfilled、rejected,Promise还应该有处理的数据和拒绝的原因。添加了这些信息的初步代码如下:
function Promise(excutor) {
this.state = 'pending';
this.value = null;
this.reason = null;
const resolve = value => {
this.value = value;
};
const reject = reason => {
this.reason = reason;
};
excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
onfulfilled(this.value);
onreject(this.reason);
}
这一版代码只是初步的将Promise对象应该具有的信息添加进去,但是并没有实现Promise状态改变和异步处理的逻辑。
首先来看Promise状态的改变。我们知道Promise的状态只能从pendding
变成fulfilled
或者rejected
,并且这个过程是不可逆的。而且,onfulfilled
函数只能在状态变成fulfilled
时才会执行,并且只执行一次;onreject
函数只能在状态变成rejected
时才会执行,并且只执行一次。我们需要加一个简单的判断。代码如下:
function Promise(excutor) {
this.state = 'pending';
this.value = null;
this.reason = null;
const resolve = value => {
if(this.state === 'pending'){
this.value = value;
this.state = 'fulfilled';
}
};
const reject = reason => {
if(this.state === 'pending'){
this.value = reason;
this.state = 'rejected';
}
};
excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
if(this.state === 'fulfilled') {
onfulfilled(this.value);
}
if(this.state === 'rejected') {
onreject(this.reason);
}
}
在解决了Promise状态改变的问题之后,我们开始着手解决Promise异步编程的问题。异步编程要解决的问题就是.then()
方法中定义的函数不能直接调用,要满足某种条件之后才可以调用。那怎么办呢?我们应该在合适的时间去调用onfulfilled
方法,这个合适的时间应该是开发者调用resolve()
的时刻。这段代码这样来写:
function Promise(excutor) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledFunc = Function.prototype;
this.onRejectedFunc = Function.prototype;
const resolve = value => {
if(this.state === 'pending'){
this.value = value;
this.state = 'fulfilled';
this.onFunfilledFunc(this.value);
}
};
const reject = reason => {
if(this.state === 'pending'){
this.value = reason;
this.state = 'rejected';
this.onRejectedFunc(this.reason);
}
};
excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
if(this.state === 'fulfilled') {
onfulfilled(this.value);
}
if(this.state === 'rejected') {
onreject(this.reason);
}
if(this.state === 'pending') {
this.onFulfilledFunc = onfulfilled;
this.onRejectedFunc = onrejected;
}
}
这样,我们的代码就可以异步执行了。但是,这一版的代码还是存在一些小的缺陷。举个栗子
let p = new Promise((resolve, reject) => {
resolve('data');
});
p.then(data => {
console.log(data);
});
console.log(1);
在正常的Promise中,这段代码的输出应该是先输出1,然后再输出data;而在我们自己的Promise中,则是先输出data,后输出1。这跟两行代码放入事件循环队列的顺序有关。在我们的代码中,先定义了.then()
方法,因此,它先被放入事件循环队列。而在实际的Promise中,将所有同步的代码都变成了异步的,所以.then()
方法应该是要放到所有同步执行的代码之后的。我们这样改进:
function Promise(excutor) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledFunc = Function.prototype;
this.onRejectedFunc = Function.prototype;
const resolve = value => {
if(value instanceof Promise) {
return value.then(resolve, reject);
}
setTimeout(() => {
if(this.state === 'pending'){
this.value = value;
this.state = 'fulfilled';
this.onFunfilledFunc(this.value);
}
});
};
const reject = reason => {
setTimeout(() => {
if(this.state === 'pending'){
this.value = value;
this.state = 'rejected';
this.onRejectedFunc(this.reason);
}
});
};
excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
if(this.state === 'fulfilled') {
onfulfilled(this.value);
}
if(this.state === 'rejected') {
onreject(this.reason);
}
if(this.state === 'pending') {
this.onFulfilledFunc = onfulfilled;
this.onRejectedFunc = onrejected;
}
}
这样就可以正常实现了。除此之外,正常的Promise中还可以添加多个.then()
方法,然后依次执行多个.then()
方法。而我们的Promise则会覆盖掉旧的方法。因此,我们采用数组将多个方法存起来,然后依次执行。代码如下:
function Promise(excutor) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledArray = [];
this.onRejectedArray = [];
const resolve = value => {
if(value instanceof Promise) {
return value.then(resolve, reject);
}
setTimeout(() => {
if(this.state === 'pending'){
this.value = value;
this.state = 'fulfilled';
this.onFunfilledArray.forEach(func => {
func(value)
});
}
});
};
const reject = reason => {
setTimeout(() => {
if(this.state === 'pending'){
this.value = value;
this.state = 'rejected';
this.onRejectedArray.forEach(func => {
func(reason)
});
}
});
};
excutor(resolve, reject);
}
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
if(this.state === 'fulfilled') {
onfulfilled(this.value);
}
if(this.state === 'rejected') {
onreject(this.reason);
}
if(this.state === 'pending') {
this.onFulfilledFunc = onfulfilled;
this.onRejectedFunc = onrejected;
}
}
最后一个需要完善的细节是,在构造函数中如果出错,将会自动触发Promise实例状态变为rejected,在这里,我们用try...catch...
模块包裹excutor。
try {
excutor(resolve, reject)
} catch(e) {
reject(e)
}
到此,我们的Promise骨架就基本实现了。现在,我们来总结一下Promise骨架实现的几个要点:
- Promise的状态具有凝固性
- Promise可以在then方法第二个参数中进行错误处理
- Promise实例可以添加多个then处理场景
4.2 Promise 链式调用
链式调用的本质就是返回一个Promise对象。因此,我们的代码这样修改.
- 首先,我们在then方法中创建一个新的Promise实例并返回。代码如下:
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
// promise2将作为then方法的返回值
let promise2;
if(this.state === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.value);
resolve(result);
} catch(e) {
reject(e);
}
});
});
}
if(this.state === 'rejected'){
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.reason);
resolve(result);
} catch(e) {
reject(e);
}
});
});
}
if(this.state === 'pending') {
return promise2 = new Promise(resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value);
resolve(result);
} catch(e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason);
resolve(result);
} catch(e) {
reject(e);
}
});
});
}
}
有的时候,Promise的链式调用会变得很复杂。在这里,我们抽象一个函数resolvePromise()
来处理Promise.这个函数需要传入四个参数:
- promise2:返回的Promise实例
- result:onfulfilled或onrejected函数的返回值
- resolve:promise2的resolve方法
- reject:promise2的reject方法
代码如下:
const resolvePromise = (promise2, result, resolve, reject) => {
};
Promise.prototype.then = function(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data;
onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error};
// promise2将作为then方法的返回值
let promise2;
if(this.state === 'fulfilled') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onfulfilled(this.value);
resolvePromise(promise2, result, resolve,reject);
} catch(e) {
reject(e);
}
});
});
}
if(this.state === 'rejected'){
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.reason);
resolvePromise(promise2, result, resolve,reject);
} catch(e) {
reject(e);
}
});
});
}
if(this.state === 'pending') {
return promise2 = new Promise(resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value);
resolvePromise(promise2, result, resolve,reject);
} catch(e) {
reject(e);
}
});
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason);
resolvePromise(promise2, result, resolve,reject);
} catch(e) {
reject(e);
}
});
});
}
};
然后,我们来实现这个函数。废话不多说,上代码:
const resolvePromise = (promise2, result, resolve, reject) => {
// 当result === promise2,也就是在onfulfilled返回promise2时,执行reject
if(result === promise2) {
reject(new TypeError('error due to circular reference'));
}
// 是否已经执行过onfulfilled或onrejected
let consumed = false;
let thenable;
if(result instanceof Promise) {
if(result.state === 'pending'){
result.then(function(data) {
resolvePromise(promise2, data, resolve, reject)
});
} else {
result.then(resolve, reject);
}
return;
}
let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null);
if(isComplexResult(result)){
try {
thenable = result.then;
// 判断返回值是否为 Promise 类型
if(typeof thenable === 'function') {
thenable.call(result, function(data) {
if(consumed){
return;
}
consumed = true;
return resolvePromise(promise2, data, resolve, reject);
}, function(error) {
if(consumed) {
return;
}
cosumed = true;
return reject(error);
});
} else {
resolve(result);
}
} catch(e) {
if(consumed) {
return;
}
consume = true;
return reject(e);
}
} else {
resolve(result);
}
};
总结一下,这段代码主要集中对于Promise决议之后返回一个Promise对象的问题进行了解决,实现了Promise的链式调用。解决途径是,如果返回的值是Promise类型的,则持续调用resolvePromise
函数进行决议,直到遇到非Promise类型的值。遇到一般值,则直接调用resolve
方法进行决议。
4.3 Promise 其他方法实现
Promise.prototype.catch()
Promise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc);
};
Promise.resolve()
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value);
});
};
Promise.reject()
Promise.reject = function(value) {
return new Promise((resolve, reject) => {
reject(value);
});
};
Promise.all()
Promise.all = function(promiseArray) {
if(!Array.isArray(promiseArray)){
throw new TypeError('The arguments should be an array');
}
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for(let i = 0; i < length; i++) {
promiseArrayp[i].then(data => {
resultArray.push(data)
if(resultArray.length === length) {
resolve(resultArray);
}
}, reject);
}
} catch(e) {
reject(e);
}
});
};
Promise.race()
Promise.race = function(value) {
if(!Array.isArray(promiseArray)){
throw new TypeError('The arguments should be an array');
}
return new Promise((resolve, reject) => {
try {
const length = promiseArray.length
for(let i = 0; i < length; i++) {
promiseArrayp[i].then(resolve, reject);
}
} catch(e) {
reject(e);
}
});
};