代码实现
function _Promise(fn) {
this.status = "pending";
this.data = null;
const _this = this;
this.cacheFn = [];
this.errorObj = {
fn: null
}
function resolve(data) {
_this.status = "resolved";
_this.data = data;
if (_this.cacheFn.length > 0) {
const fn = _this.cacheFn.shift();
const result = fn(data);
return _this.resultHandler(result);
}
}
function reject(error) {
if (_this.errorObj.fn) {
_this.errorObj.fn(error)
} else {
_this.status = "rejected";
_this.errorObj.error = error;
}
}
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
_Promise.prototype.resultHandler = function (result, isThen) {
if (result instanceof _Promise) {
if (result.errorObj.error !== undefined) {
this.errorObj.error = result.errorObj.error;
}
result.errorObj = this.errorObj;
if (result.status === "resolved") {//同步操作
const thenFun = this.cacheFn.shift();
result.cacheFn = this.cacheFn;
result.then(thenFun);
} else if (result.status === "pending") {//异步操作
if (isThen) { //then函数中的调用
if (this.cacheFn.length > 0) {
result.cacheFn = this.cacheFn;
} else { //第一个promise对象是同步的操作
return result;
}
} else {
result.cacheFn = this.cacheFn;
}
} else if (result.status === "rejected") {//出错了
if (result.errorObj && result.errorObj.fn) {
result.errorObj.fn(result.errorObj.error);
}
}
}
}
_Promise.prototype.catch = function (fn) {
if (typeof fn !== "function") {
return false;
}
if (this.errorObj) {
this.errorObj.fn = fn;
}
}
_Promise.prototype.then = function (fn) {
if (this.status === "resolved") { //执行完毕了
const result = fn(this.data);
return this.resultHandler(result, true);
} else if (this.status === "pending") {
this.cacheFn.push(fn);
}
return this;
}
_Promise.all = function (fn_arr) {
if (Array.isArray(!fn_arr)) {
throw new Error("参数必须要传递一个数组!");
}
let arr = [];
let cache_resolve = null;
let cache_reject = null;
let hasError = false;
fn_arr.forEach((promise, index) => {
const i = index;
promise.then((value) => {
arr[i] = value
if (arr.length === fn_arr.length) {
cache_resolve(arr);
}
}).catch((e) => {
if (hasError) {
return false;
}
hasError = true;
cache_reject(e);
})
})
return new _Promise((resolve, reject) => {
cache_resolve = resolve;
cache_reject = reject;
});
}
验证代码
function test(value) {
return new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(value);
}, 2000);
})
}
_Promise.all([test(200), test(400), test(700)]).then((value) => {
console.log(value)
}).catch((e) => {
console.log(e);
})
test(1000).then((value) => {
console.log(1);
return test(value + 100);
}).then((value) => {
console.log(2);
return new _Promise((resolve, reject) => {
resolve(value + 500);
})
}).then((value) => {
console.log(3);
return test(value + 100);
})
.then((value) => {
console.log(value);
}).catch((e) => {
console.log(e);
})
分析
简单模式
实现一个Promise的延时调用的特性很容易,我们给每一个Promise对象赋予一个状态存在三个值分别是:pending,resolved和rejected.实例化一个Promise对象的时候它的初始状态为pending并赋予一个data属性为null.当用户创建一个Promise对象的时候它对应可能存在的两种情况,第一种是在Promise函数中执行同步操作,第二种是异步操作.如果是同步操作的话我们其实很容易处理,同步操作的执行顺序是先调用resolve函数再调用then函数中的参数fn函数,当我们在设计这部分的代码时就要保证调用完resolve函数后得到返回值赋予this.data缓存起来,随后用户调用then函数中的参数fn函数中可以获取到刚才缓存的数据this.data去做下一步的操作,如此一个同步的操作流程就结束了.如果是异步操作,代码会先执行then函数,由于此时resolve函数还没执行,所以先把then函数中用户填入的函数参数fn保存起来,等resolve异步函数的代码调用完毕我们可以在resolve函数中获取刚才缓存的函数并将参数传递给它执行.
链式调用
上面描述的实现过程只适合一个promise对象一个then函数的调用模式.如果用户在then函数内再返回一个Promise对象我们就要想办法来实现链式调用.首先要克服的第一个问题是开发者可以写形如这样的代码.then(fn).then(fn).then(fn),它可以在后面一直接.then的函数调用我们如何保证这样的语法不出错呢.这种链式调用函数首先想到的是函数柯里化,在函数中返回一个对象而这个对象包含then函数.我们采用的是第二种方法就是返回一个promise对象,因为它一定包含then函数可以满足这样链式调用的模式.我们在then函数中返回this把当前promise对象作为返回值让它继续去调用then函数,那么这个then函数的编写就是实现链式调用的核心了.
假如开发者此时写了一个异步操作的Promise并调用了then函数,我们在编写这个then函数的逻辑就会遇到一个很棘手的问题,因为此时resolve函数还没执行,而已经开始执行then函数了,正常的逻辑应该是执行then函数的时候resolve函数已经执行并把返回值传给我了.此时既然做不到等待resolve函数执行完我们就先把传递给then函数的参数fn(用户编写的函数)先存到数组this.cacheFn当中去,返回当前对象this继续执行then函数又将第二个调用链条的fn存入到this.cacheFn中,这样一来用户编写的所有then函数链中的作为参数的函数都被缓存在数组中了.我们现在要做的就是按照数组的顺序一个接一个的去执行就实现了链式调用.过了一段时间第一个Promise对象的异步代码终于走完了它开始调用resolve函数了,在resolve函数中获取到this.cacheFn的第一个函数fn并传入resolve函数的参数值作为fn的参数运行.fn调用它可能返回一个Promise对象或者其他值,如果是其他值的话说明调用链到此结束了.如果返回值是一个Promise对象,那么这个Promise对象里面执行的代码又分两种情况同步和异步.此时异步的情况比较好处理直接将this.cacheFn挂载到该对象上就可以了,因为这个异步操作执行完又会执行resolve函数会再次获得上一个Promise对象传递过来的this.cacheFn继续进行链条函数调用.如果是同步情况下resolve已经执行完了,需要我们手动触发then函数的执行,我们需要将this.cacheFn剥离第一个函数给then函数作为参数执行它并将this.cacheFn挂载到该对象上,接下来的执行过程就是不断重复上面说的流程.
整个实现过程的目标就是要按照顺序执行完this.cacheFn数组装载的所有函数就实现了链式调用,难点在于不同的promise对象如何共用一份this.cacheFn.具体的实现思路是将this.cacheFn从最初的promise对象起逐级的传递给后面的promise对象使用.