JavaScript異步編程:幫助弗利薩完成8次變身

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

  • 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鏈持續到第八次變身完畢。github


用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。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+生成器的語法層面實現。可讓咱們省略背後的細節,直接採用同步寫法編寫異步程序。dom

async function handleAsync() {
            while(Frieza.getState() !== 8){
                try {
                    await keepChange();
                } catch (error) {
                    console.log(error)
                }
            }
            console.log('成功!');
           
        } 
        handleAsync(); */

這樣就能夠了,幾乎與promise+與生成器的業務寫法如出一轍。異步


用rxjs幫助弗利薩

用上rxjs的觀察者模式後,實際上就能夠把弗利薩的change函數裏面的callback給解耦出來。把這部分的邏輯交給觀察者處理裏面。而弗利薩只要在每次變身成功或者失敗時發出通知就好了。
具體步驟以下async

  1. 建立一個能夠供你們收看的電視節目'dragonBall',這個被咱們叫作七龍珠的電視節目(subject)能夠被觀衆們訂閱,同時,這個電視節目也能爲所欲爲的播放他想要給觀衆們看到的東西。異步編程

    const dragonBall = new Rx.Subject();
  2. 讓弗利薩在變身完成或失敗時,經過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)
                }
  3. 收看dragonBall,而且在弗利薩沒變到第8形態前,持續地讓弗利薩變身。

    const watchAnime = dragonBall.asObservable()
            
            .subscribe(message => {
                console.log(message);
                
                if (Frieza.getState() !== 8) {
                    Frieza.change();
                } else {
                    watchAnime.unsubscribe();
                }
    
            })
  4. 讓弗利薩開始變身

    Frieza.change();

前往github查看示例代碼
相關文章
相關標籤/搜索