那么,我们发现 ——
除了版本一之外,版本二、三、四全都跪了……
那是否意味着我们要回归到版本一呢?
当然并不是。
const traffic = document.getElementById("traffic");
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state){
traffic.className = state;
}
function reset(){
Promise.resolve()
.then(setState.bind(null, "wait"))
.then(wait.bind(null, 1000))
.then(setState.bind(null, "stop"))
.then(wait.bind(null, 2000))
.then(setState.bind(null, "pass"))
.then(wait.bind(null, 3000))
.then(reset);
}
reset();
版本五的思路是,既然我们需要考虑不同的持续时间,那么我们需要将等待时间抽象出来:
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
这一版本里我们用了 Promise 来处理回调问题,当然对 ES6 之前的版本,可以用 shim 或 polyfill、第三方库,也可以选择不用 Promise。
版本五抽象出的 wait 方法也还比较通用,可以用在其他地方。这是版本五好的一点。
我们还可以进一步抽象,设计出版本六,或者类似的对象模型:
const trafficEl = document.getElementById("traffic");
function TrafficProtocol(el, reset){
this.subject = el;
this.autoReset = reset;
this.stateList = [];
}
TrafficProtocol.prototype.putState = function(fn){
this.stateList.push(fn);
}
TrafficProtocol.prototype.reset = function(){
let subject = this.subject;
this.statePromise = Promise.resolve();
this.stateList.forEach((stateFn) => {
this.statePromise = this.statePromise.then(()=>{
return new Promise(resolve => {
stateFn(subject, resolve);
});
});
});
if(this.autoReset){
this.statePromise.then(this.reset.bind(this));
}
}
TrafficProtocol.prototype.start = function(){
this.reset();
}
var traffic = new TrafficProtocol(trafficEl, true);
traffic.putState(function(subject, next){
subject.className = "wait";
setTimeout(next, 1000);
});
traffic.putState(function(subject, next){
subject.className = "stop";
setTimeout(next, 2000);
});
traffic.putState(function(subject, next){
subject.className = "pass";
setTimeout(next, 3000);
});
traffic.start();
这一版本里,我们设计了一个 TrafficProtocol 类,它有 putState、reset、start 三个方法:
putState 接受一个函数作为参数,这个函数自身有两个参数,一个是 subject,是由 TrafficProtocol 对象初始化时设定的 DOM 元素,一个是 next,是一个函数,表示结束当前 state,进入下一个 state。
reset 结束当前状态循环,开始新的循环。
start 开始执行循环,这里的实现是直接调用 reset。
看一下 reset 的实现思路:
TrafficProtocol.prototype.reset = function(){
let subject = this.subject;
this.statePromise = Promise.resolve();
this.stateList.forEach((stateFn) => {
this.statePromise = this.statePromise.then(()=>{
return new Promise(resolve => {
stateFn(subject, resolve);
});
});
});
if(this.autoReset){
this.statePromise.then(this.reset.bind(this));
}
}
在这里我们创建一个 statePromise,然后将 stateList 中的方法(通过 putState 添加的)依次绑定到 promise 上。如果设置了 autoReset,那么我们在 promise 的最后绑定 reset 自身,这样就实现了循环切换。
有了这个模型,我们要添加新的状态,只需要通过 putState 添加一个新的状态就好了。这一模型不仅仅可以用在这个需求里,还可以用在任何需要顺序执行异步请求的地方。
最后,我们看到,版本六用到了面向对象、过程抽象、Promise等模式,它的优点是 API 设计灵活,通用性和扩展性好。但是版本六也有缺点,它的实现复杂度比前面的几个版本都高,我们在做这样的设计时,也需要考虑是否有过度设计的嫌疑。
· 设计是把双刃剑,繁简需要权衡,尺度需要把握。
写代码简单,程序设计不易,需要走心。