从零实现一个简易的 Promise
所有问题都可以通过加一层中间层来解决。
简易的,不做废话直接开始 :)
const p = new Promise((resolve, reject)=>{
// 如果操作成功则调用 resolve 并传入 value
// 如果操作失败则调用 reject 并传入 reason
});
通常我们都会使用上述方法获取 Promise 实例:在构造函数种传入一个 executor 方法,当同步/异步的任务完成时调用 resolve,失败时调用 reject,简单易懂,在此不多赘述。
状态机
一个 Promise 可以理解为一个状态机,相应的 API 接口要么用于改变状态机的状态,要么在到达某个状态时被触发,因此首先需要实现的是 Promise 的状态信息:
const PENDING = 0
const FULFILLED = 1
const REJECTED = 2
并且只存在 PENDING => FULFILLED 或者 PENDING => REJECTED 的状态转移。
构造函数
首先实现构造函数的框架如下:
class Promise {
constructor(executor) {
this.status = PENDING; // 实例当前的状态
this.data = undefined; // Promise 返回的值
this.defered = []; // 回调函数集
executor(resolve, reject); // 执行 executor 并传入相应的参数
}
}
上述代码基本实现 Promise 构造函数的主题部分,但是存在三个问题:
- resolve 和 reject 参数/方法尚未定义
- executor 函数体中可能会抛出异常,需要做容错处理
- 考虑 executor 函数体中在调用 resolve/reject 时的 this 指向问题
修修补补如下:
class Promise {
constructor(executor) {
this.status = PENDING;
this.data = undefined;
this.defered = [];
try {
// bind, bind, bind!
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
resolve(value) {
// TODO
}
reject(reason) {
// TODO
}
}
resolve & reject
接下来实现 resolve 和 reject 方法,基本上就是在判断状态为 PENDING 之后把状态改为相应的值,并把对应的 value 和 reason 存在 self 的 data 属性上面,最后执行相应的回调函数,逻辑很简单:
resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED;
this.data = value;
this.defered.forEach(i => i.onfulfiled(value));
}
}
reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED;
this.data = reason;
this.defered.forEach(i => i.onrejected(reason));
}
}
then 方法
Promise 对象有一个 then 方法,用来注册在这个 Promise 状态确定后的回调,很明显 then 方法需要写在原型链上,Promise 总共有三种可能的状态,在 then 方法中我们分别用三个判断分支来处理,并且都分别返回一个新的 Promise 实例。
then(onResolved, onRejected) {
// 如果 then 的参数不是 function 则我们需要忽略它
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {};
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};
switch (this.status) {
case FULFILLED:
return new Promise((resolve, reject) => {
// TODO
});
case REJECTED:
return new Promise((resolve, reject) => {
// TODO
});
case PENDING:
return new Promise((resolve, reject) => {
// TODO
});
}
}
完整的实现如下,其中需要注意的是,如果 onResolved 的返回值是一个 Promise 对象,则直接取它的结果做为新的 Promise 实例的结果:
then(onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {};
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};
switch (this.status) {
case FULFILLED:
return new Promise((resolve, reject) => {
try {
const r = onResolved(this.data);
r instanceof Promise && r.then(resolve, reject);
resolve(r);
} catch (e) {
reject(e);
}
});
case REJECTED:
return new Promise((resolve, reject) => {
try {
const r = onRejected(this.data);
r instanceof Promise && r.then(resolve, reject);
} catch (e) {
reject(e);
}
});
case PENDING:
return new Promise((resolve, reject) => {
const onfulfiled = () => {
try {
const r = onResolved(this.data);
r instanceof Promise && r.then(resolve, reject);
} catch (e) {
reject(e);
}
};
const onrejected = () => {
try {
const r = onRejected(this.data);
r instanceof Promise && r.then(resolve, reject);
} catch (e) {
reject(e);
}
};
this.defered.push({
onfulfiled,
onrejected
});
});
}
}
至此实现一个简易的 Promise,使用如下测试用例验证:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}).then((res) => {
console.log(res);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
}).then((res) => {
console.log(res);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 1000);
});
}).then((res) => {
console.log(res);
});
// 1
// 2
// 3
// [Finished in 3.1s]
其他问题(UPDATED)
异步问题
new Promise((resolve) => {
resolve();
})
.then(() => {
console.log('1');
})
.then(() => {
console.log('2');
});
console.log('3');
执行上面的代码会发现输出的顺序是“1, 2, 3”,而不是正确的“3, 1, 2”,显然是因为我们没有在 Promise 的 resolve 方法中异步的调用回调函数集导致的,当然解决这个问题也很简单,就是使用 setTimeout,但是这样实现的话并不符合 Promise 在事件循环中的优先级,所以暂时忽略。
值穿透问题
new Promise((resolve) => {
resolve(8);
})
.then()
.then()
.then((value) => {
console.log(value)
});
上面的代码使用我们刚刚实现的 Promise 会打印 undefined,然而这并不是我们期望得到的结果,我们希望的是8这个值会穿过两个 then 到达链尾的 then 的执行函数里,其输出应该和这段代码一致:
new Promise((resolve) => {
resolve(8);
})
.then((value) => {
return value;
})
.then((value) => {
return value;
})
.then((value) => {
console.log(value);
});
其实要实现这个功能十分简单,只要把 then 的两个参数的默认值做简单的修改:
onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return v; };
onRejected = typeof onRejected === 'function' ? onRejected : function(r) { return r; };
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。