1
这边文章试图通过一个例子展示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给解耦出来。把这部分的逻辑交给观察者处理里面。而弗利萨只要在每次变身成功或者失败时发出通知就行了。
具体步骤如下

  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查看示例代码

野生爬山虎
134 声望6 粉丝

野生前端玩家


« 上一篇
js原型链
下一篇 »
ngxs入门