這邊文章試圖經過一個例子展現javascript異步編程的幾種寫法。
弗利薩必需要從第1形態過渡到第8形態纔有可能擊敗賽亞人,每一次變身成下一形態須要1秒鐘,在這期間他可能會遭受到賽亞人的攻擊,若是在變身過程當中受到傷害,他將被打回第一形態。請幫助弗利薩徹底變身成第8形態,擊敗賽亞人。javascript
開始建立一個弗利薩java
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) } } })();
以上代碼用當即執行函數建立了一個弗利薩:git
接下來須要寫一些代碼來幫助弗利薩持之以恆的變身,直到他成功變身到第8形態。示例最終會按下圖的樣子在控制檯中呈現。
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鏈持續到第八次變身完畢。github
// 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。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是promise+生成器的語法層面實現。可讓咱們省略背後的細節,直接採用同步寫法編寫異步程序。dom
async function handleAsync() { while(Frieza.getState() !== 8){ try { await keepChange(); } catch (error) { console.log(error) } } console.log('成功!'); } handleAsync(); */
這樣就能夠了,幾乎與promise+與生成器的業務寫法如出一轍。異步
用上rxjs的觀察者模式後,實際上就能夠把弗利薩的change函數裏面的callback給解耦出來。把這部分的邏輯交給觀察者處理裏面。而弗利薩只要在每次變身成功或者失敗時發出通知就好了。
具體步驟以下:async
建立一個能夠供你們收看的電視節目'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查看示例代碼