这边文章试图通过一个例子展示javascript异步编程的几种写法。
示例说明
弗利萨必须要从第1形态过渡到第8形态才有可能击败赛亚人,每一次变身成下一形态需要1秒钟,在这期间他可能会遭受到赛亚人的攻击,如果在变身过程中受到伤害,他将被打回第一形态。请帮助弗利萨完全变身成第8形态,击败赛亚人。
弗利萨的单例对象
开始创建一个弗利萨
const Frieza = (function () {
var state = 1; //内部变量,表示弗利萨的当前形态
return {
chargingFlag: false, //表示弗利萨是否正在变身中
damage: function () { //20%几率收到伤害
return Math.random() < 0.2;
},
stateReset: function () { //打回第一形态
state = 1;
},
getState: function () { //外部访问state的接口函数
return state;
},
change: function (callback) { //变身
if (this.chargingFlag === true) {
throw new Error(`弗利萨还没变身完毕呢`);
}
this.chargingFlag = true;
console.log(`弗利萨开始进行第${state + 1}形态变身`)
setTimeout(() => { //每一阶段变身消耗1秒
if (this.damage()) {
this.stateReset();
this.chargingFlag = false;
callback('变身被悟空打断啦!');
return;
}
state++;
this.chargingFlag = false;
callback(null, state);
}, 1000)
}
}
})();
以上代码用立即执行函数创建了一个弗利萨:
- state为内部变量,表示弗利萨当前的形态,初始为第一形态,并且设置了一个接口(getState函数)供外部了解弗利萨当前的形态。
- change函数实现了弗利萨的变身,必须等到一次变身完毕后才能再次变身,每一次变身需要1秒钟。
- 在每一次变身完毕后会执行回调函数,我们规定回调函数有两个参数,第一个表示变身失败,被打断时应当传入的参数,第二个表示变身成功时应当传入的参数。
接下来需要写一些代码来帮助弗利萨锲而不舍的变身,直到他成功变身到第8形态。示例最终会按下图的样子在控制台中呈现。
用Promise帮助弗利萨:
function keepChange() {
return new Promise((resolve, reject) => {
Frieza.change((err, state) => {
if (err) {
reject(err);
} else {
resolve(state);
}
})
})
}
function handelKeepChange() {
keepChange().then((x) => {
if(x !== 8){
handelKeepChange();
} else {
console.log('成功!')
}
}).catch(err => {
console.log(err);
handelKeepChange();
})
}
handelKeepChange();
看上去已经不错了,这已经比直接在回调函数里面写回调函数要好得多了,我们通过递归调用handelKeepChange,让这条Promise链持续到第八次变身完毕。
用Promise + 生成器帮助弗利萨
// generator + promise
function* async() {
while (Frieza.getState() !== 8) {
yield keepChange();
}
console.log('成功!');
}
function keepChange() {
return new Promise((resolve, reject) => {
Frieza.change((err, state) => {
if (err) {
reject(err);
} else {
resolve(state);
}
})
})
}
function handleAsync(asyncFn) {
const ita = asyncFn();
function handle(v) {
if (!v.done) {
v.value.then(state => {
handle(ita.next(state));
}).catch(err => {
console.log(err);
handle(ita.next());
})
}
}
handle(ita.next());
}
handleAsync(async);
这种用生成器+promise的写法比纯用promise的写法要复杂一些,但是因为利用了生成器的特性,使得我们在执行具体的异步业务时,可以写的比较优雅:
function* async() {
while (Frieza.getState() !== 8) {
yield keepChange();
}
console.log('成功!');
}
这种写法比较有亲和力,逻辑上比较清晰。它内部的实现是通过一个handleAsync函数不断地递归调用handle函数,从而让生成器能在一次Promis承诺实现后让生成器继续产出下一次Promise。
function handle(v) {
if (!v.done) { // 如果生成器还没结束,那么就继续产出一个promise
v.value.then(state => {
handle(ita.next(state));
}).catch(err => {
console.log(err);
handle(ita.next());
})
}
}
用async await帮助弗利萨
async await是promise+生成器的语法层面实现。可以让我们省略背后的细节,直接采用同步写法编写异步程序。
async function handleAsync() {
while(Frieza.getState() !== 8){
try {
await keepChange();
} catch (error) {
console.log(error)
}
}
console.log('成功!');
}
handleAsync(); */
这样就可以了,几乎与promise+与生成器的业务写法一模一样。
用rxjs帮助弗利萨
用上rxjs的观察者模式后,实际上就可以把弗利萨的change函数里面的callback给解耦出来。把这部分的逻辑交给观察者处理里面。而弗利萨只要在每次变身成功或者失败时发出通知就行了。
具体步骤如下:
-
创建一个可以供大家收看的电视节目'dragonBall',这个被我们叫做七龙珠的电视节目(subject)可以被观众们订阅,同时,这个电视节目也能随心所欲的播放他想要给观众们看到的东西。
const dragonBall = new Rx.Subject();
-
让弗利萨在变身完成或失败时,通过dragnonBall这个subject,告知所有收看该节目的观众他变身失败,或者成功了。修改弗利萨的change函数:
change: function () { if (this.chargingFlag === true) { drangonBall.next(new Error('变身还没结束呢!')) } this.chargingFlag = true; console.log(`弗利萨开始进行第${state + 1}形态变身`) setTimeout(() => { if (this.damage()) { this.stateReset(); this.chargingFlag = false; dragonBall.next(new Error('变身被悟空打断啦!')); return; } state++; this.chargingFlag = false; dragonBall.next(`${state}形态变身成功!`) }, 1000) }
-
收看dragonBall,并且在弗利萨没变到第8形态前,持续地让弗利萨变身。
const watchAnime = dragonBall.asObservable() .subscribe(message => { console.log(message); if (Frieza.getState() !== 8) { Frieza.change(); } else { watchAnime.unsubscribe(); } })
-
让弗利萨开始变身
Frieza.change();
前往github查看示例代码
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。