這邊文章試圖經由過程一個例子展現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();