setTimeout:
为了保证程序执行一致性的可测试性
let cache;
!function set() {
// 这里可能是同步代码,也可能是异步代码
cache = 100;
setTimeout(() =>{
cache = 100;
})
}();
console.log(cache); // 如果是同步,这里就能得到 100,如果是异步,这里就是 undefined
所以添加一个 setTimeout 包裹,就都是异步代码,保证其一致性。
值的穿透例子:
p.then()
.then(function(value) {
console.log(value);
}, function(err) {
console.log(err)
})
// 值仍然能够传递下去
循环引用例子:
let p2 = p.then(function(value) {
console.log(value);
return p;
}, function(err) {
console.log(err)
})
整体结构
- Promise 是一个类
- 需要传入一个回调函数
- 实例中保存一个状态,初始就是 pending
- 创建实例的时候,回调函数立即执行
- 回调函数执行传入 res 和 rej 两个回调函数
//=> 接受一个回调函数
function Promise(fn) {
let _this = this; // 为了确保函数中的 this 是实例,在函数中使用 _this
this.status = 'pending';
function resolve(value) {
//...
}
function reject(err) {
//...
}
//=> 创建实例时,立即执行 fn
fn(resolve, reject);
}
参数处理
由于 rej 和 res 两个函数接收的参数,需要在 then 中的回调函数中传入,所以需要在实例中保存起来。
function Promise(fn) {
//=> 传给成功和失败函数参数的初始值
this.value = undefined;
this.err = undefined;
//=> 然后再 reject 或者 resolve 执行时,保存传入的参数
function resolve(value) {
//...
_this.value = value;
}
function reject(err) {
//...
_this.err = err;
}
}
报错处理
如果 Promise 中的回调函数的代码执行过程中,出现报错,那么应该直接执行失败函数,并且把错误信息传入。
function Promise(fn) {
//...
try {
fn(resolve, reject);
} catch(e) {
reject(e);
}
}
状态处理
在执行了 rej 或者 res 函数后,需要把状态变更为 resolved 或者 rejected。
function Promise(fn) {
//...
function resolve(value) {
//...
_this.status = 'resolved';
}
function reject(err) {
//...
_this.status = 'rejected';
}
}
状态凝固
如果状态从 pending 变为 rejected 或者 resolved,那么状态就会凝固,不会再改变。
只需要在 rej 和 res 函数执行时,增加判断即可,如果是 pending 才执行,如果状态已经改变为别的,就什么都不做。
防止多次调用 res 和 rej 函数。
function Promise(fn) {
//...
function resolve(value) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
//...
}
}
function reject(err) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
//...
}
}
}
then 方法
- then 方法需要传入两个回调函数
- 如果状态为 resolve,执行成功回调
- 如果状态为 reject,执行失败回调
- 如果只传递一个或没有传参,做容错处理
Promise.prototype.then = function then(success, fail) {
switch (this.status) {
case 'resolved':
success && success(this.value);
break;
case 'rejected':
fail && fail(this.err);
break;
}
}
异步处理
要使得在异步操作执行完成之后,再执行相应的回调函数,也就是在 res 或者 rej 执行的时候(导致状态修改的情况下),才去执行相应的回调。
这时,需要借助发布订阅模式的思想,来处理异步是否完成。
function Promise(fn) {
//...
//=> 存储成功和失败的回调,利用了订阅发布模式思想,这里建立事件池
this.resCallbacks = [];
this.rejCallbacks = [];
function resolve(value) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
//...
//=> 执行相应事件池中的所有事件,相当于 发布
// 每个事件函数执行传入相应的参数
_this.resCallbacks.forEach(item => {
item && item(_this.value);
});
}
}
function reject(err) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
//...
_this.rejCallbacks.forEach(item => {
item && item(_this.err);
})
}
}
//...
}
在 then 中,同步操作执行完毕,没有执行 res 或者 rej,其状态还是 pending,所以需要在状态为 pending 时,进行处理,在事件池中添加相应的事件。
Promise.prototype.then = function then(success, fail) {
switch (this.status) {
//...
case 'pending':
// 处理异步操作
this.resCallbacks.push(success);
this.rejCallbacks.push(fail);
}
}
链式操作处理
想要实现链式的操作,那么 then 就必须返回一个 Promise 实例
代码
//=> 接受一个回调函数
function Promise(fn) {
//=> 初始状态
let _this = this;
this.status = 'pending';
//=> 传给成功和失败函数参数的初始值
this.value = undefined;
this.err = undefined;
//=> 存储成功和失败的回调,利用了订阅发布模式思想
this.resCallbacks = [];
this.rejCallbacks = [];
function resolve(value) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
_this.status = 'resolved';
_this.value = value;
_this.resCallbacks.forEach(item => {
item && item(_this.value);
});
}
}
function reject(err) {
//=> 只有在 pending 状态才会执行相关操作
if (_this.status === 'pending') {
_this.status = 'rejected';
_this.err = err;
_this.rejCallbacks.forEach(item => {
item && item(_this.err);
})
}
}
//=> 创建实例时,立即执行 fn
try {
fn(resolve, reject);
} catch (e) {
// 如果执行报错,则直接执行失败回调
reject(e);
}
}
Promise.prototype.then = function then(success, fail) {
switch (this.status) {
case 'resolved':
success && success(this.value);
break;
case 'rejected':
fail && fail(this.err);
break;
case 'pending':
// 处理异步操作
this.resCallbacks.push(success);
this.rejCallbacks.push(fail);
}
}
let p = new Promise(function (res, rej) {
setTimeout(() => {
res(11);
}, 1000);
console.log(1234);
}).then((data) => {
console.log(data);
}, (err) => {
console.log(err);
});