技术要点
- 链式方法调用
- 状态机制管理
- 原型方法调用
- 任务队列调度
代码实现
话不多说,上手开写。
最简单的peomise
使用Promise时必须new一个promise对象,通过then执行成功的回调
new Promise(function (resolve, reject) {
resolve(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
故promise是一个构造函数且包含then的原型方法。
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
if (this.status === 'resolved') {
resolve(this.resolveData)
} else {
reject(this.rejectData)
}
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
上述实现了简单的Promise,但是遇到包裹setTimeout时就失效了。
补充逻辑,支持setTimeout
由于setTimeout是宏任务,需等待执行完成以后在执行then的回调,因此要添加执行队列,待执行完成以后在进行then的调用
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 补充队列字段
this.queues = [];
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. 添加pendding状态判断
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else {
reject(self.rejectData)
}
})
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else {
reject(this.rejectData)
}
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)
}, 1000)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
代码通篇拷贝,只需关注测试用例2和步骤6,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。
在实际使用中,不仅有then还会有catch,经常会通过catch处理异常。
补充catch,完善逻辑
catch在Promise使用中通过链式调用相应,说明catch为原型方法,重点在于如何跳过then的链调用catch的链
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 补充队列字段
this.queues = [];
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. 添加pendding状态判断
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else {
reject(self.rejectData)
}
})
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 7.1 完善判断
reject(this.rejectData)
} else {
// 7.2 链调用需要导出自身
return this;
}
}
// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
reject(this.rejectData)
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)
}, 1000)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
代码通篇拷贝,只需关注测试用例3和步骤7,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。
测试案例3成功执行,导出this解决链的问题,且历史不受影响。
完善catch,修复链调用
但是加入setTimeout后出现报错,需解决setTimeout任务后调用then或catch
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 补充队列字段
this.queues = [];
// 8.4 补充异常队列
this.rejectedQueues = [];
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
// 8.5 补充异常队列逻辑
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn => fn());
} else if (self.queues) {
for (var i = 0; i < self.queues.length; i++) {
var func = self.queues[i].bind(self);
func();
}
}
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. 添加pendding状态判断
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else if (self.status === 'rejected' && reject) {
// 8.1 完善队列判断
reject(self.rejectData)
}
})
return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 7.1 完善判断
reject(this.rejectData)
} else {
// 7.2 链调用需要导出自身
return this;
}
}
// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
const self = this;
// 8.2 完善异常判断
if (this.status === 'pendding') {
// 8.3 补充异常队列
this.rejectedQueues.push(function () {
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)
}, 1000)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)
}, 1000)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
代码通篇拷贝,只需关注测试用例4和步骤8,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。
此时只实现了一层的链式调用,promise需要多层链式调用
多层链完善
继续补充多层链逻辑,兼容各种情况
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 补充队列字段
this.queues = [];
// 8.4 补充异常队列
this.rejectedQueues = [];
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 补充队列逻辑
if (self.queues.length) {
self.queues.forEach(fn => fn());
}
// 8.5 补充异常队列逻辑
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn => fn());
}
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. 添加pendding状态判断
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
// 9.1 resolve会返回新的Promise,在此接收
var result = resolve(self.resolveData)
if (result) {
// 9.2 判断resolve中promise的执行状态
if (result.status === 'resolved') {
// 9.3 若为成功,修改状态数据进入下一个链
self.status = 'resolved';
self.resolveData = result.resolveData;
} else {
// 9.4 若为异常,需检测是否有异常队列并执行异常队列的方法
self.status = 'rejected';
self.rejectData = result.rejectData;
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn => fn());
}
}
}
} else if (self.status === 'rejected' && reject) {
// 8.1 完善队列判断
reject(self.rejectData)
}
})
return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 7.1 完善判断
reject(this.rejectData)
} else {
// 7.2 链调用需要导出自身
return this;
}
}
// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
const self = this;
// 8.2 完善异常判断
if (this.status === 'pendding') {
// 8.3 补充异常队列
this.rejectedQueues.push(function () {
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)
}, 1000)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)
}, 1000)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例5
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(5)
}, 1000)
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
resolve(6)
})
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
reject(7)
})
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
代码通篇拷贝,只需关注测试用例5和步骤9,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。
添加setTimeout,修复链遇到宏任务失效问题
遇到setTimeout后,链的调用会失效,输出混乱。上面那个例子完善了链,但因为是同步操作,所以没有表现出问题,加入setTimeout后问题出现。
主要原因是实例指向问题,解决此问题需要对this(或实例)指向有一定的逻辑性。
function PromiseA(callback) {
// 1. 参数校验
if (!callback) throw new Error('callback is required!');
var self = this;
// 2. 定义状态 pendding, resolved, rejected
this.status = 'pendding';
// 4.2 成功或失败数据需要存储
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 补充队列字段
this.queues = [];
// 8.4 补充异常队列
this.rejectedQueues = [];
// 4.1 callback需要两个参数,此处定义
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 补充队列逻辑
if (self.queues.length) {
// 10.1 添加bind方法改变函数this指向,让this指向具体实例
for (var i = 0; i < self.queues.length; i++) {
var func = self.queues[i].bind(self);
func();
}
}
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 8.5 补充异常队列逻辑
if (self.rejectedQueues.length) {
// 10.2 添加bind方法改变函数this指向,让this指向具体实例
for (var i = 0; i < self.rejectedQueues.length; i++) {
var func = self.rejectedQueues[i].bind(self);
func();
}
} else if (self.queues) {
for (var i = 0; i < self.queues.length; i++) {
var func = self.queues[i].bind(self);
func();
}
}
}
// 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
// 6. 添加pendding状态判断
if (this.status === 'pendding') {
this.queues.push(function () {
// 10.3 删除原来定义的self,当前函数的this指向的就是实例,为了防止和外层混淆,定义新的self
var self = this;
if (self.status === 'resolved') {
// 9.1 resolve会返回新的Promise,在此接收
var result = resolve(self.resolveData)
if (result) {
// 10.4 resolve执行完成后,修改状态为pendding,防止后续then输出
self.status = 'pendding';
// 10.5 继承父级回调,删除父级回调,防止继续调用
result.queues = self.queues.slice(1)
self.queues.splice(1, self.queues.length)
result.rejectedQueues = self.rejectedQueues
// 9.2 判断resolve中promise的执行状态
if (result.status === 'resolved') {
// 9.3 若为成功,修改状态数据进入下一个链
self.status = 'resolved';
self.resolveData = result.resolveData;
} else if (result.status === 'rejected') {
// 9.4 若为异常,需检测是否有异常队列并执行异常队列的方法
self.status = 'rejected';
self.rejectData = result.rejectData;
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn => fn.bind(result)());
}
}
}
} else if (self.status === 'rejected' && reject) {
// 8.1 完善队列判断
reject(self.rejectData)
}
// 10.6 执行下一Promise的队列
if (result && result.queues) {
for (var i = 0; i < result.queues.length; i++) {
var func = result.queues[i].bind(self);
func();
}
}
})
return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 7.1 完善判断
reject(this.rejectData)
} else {
// 7.2 链调用需要导出自身
return this;
}
}
// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
// 8.2 完善异常判断
if (this.status === 'pendding') {
// 8.3 补充异常队列
this.rejectedQueues.push(function () {
// 10.7 删除原来定义的self,当前函数的this指向的就是实例,为了防止和外层混淆,定义新的self
var self = this;
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// 测试用例1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)
}, 1000)
}).then(res => {
console.log('成功' + res);
}, err => {
console.log('失败' + err);
});
// 测试用例3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)
}, 1000)
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例5
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(5)
}, 1000)
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
resolve(6)
})
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
reject(7)
})
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
// 测试用例6
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(8)
}, 1000)
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
setTimeout(function (){
resolve(9)
}, 1000)
})
}).then(res => {
console.log('成功' + res);
return new PromiseA((resolve, reject) => {
reject(10)
})
}).then(res => {
console.log('成功' + res);
}).catch(err => {
console.log('失败' + err);
});
代码通篇拷贝,只需关注测试用例6和步骤10,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。
结语
上述最后一段代码就是Promise的js实现了,测试覆盖不是很全面,所以可能会有其他的问题。
用js实现Promise主要是加深对微任务,队列,this指向,构造函数,原型等等原生js的技术加深。
有兴趣的可以自己试一试