初探Promise

一个星期没更新了,先来个小游戏来放松放松,勇者大战魔王:

考验人品的时候到了,反复进攻,看你能几次进攻打败魔王。

上面只是个小例子,如何实现上面的效果呢,这就要说到我们今天的正主了–Promise,Promise译为承诺。在ES6发布时Promise被ES6列为正式规范,成为最重要的特性之一,Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单来说就是把一个函数放进去,符合你定的规范,就返回成功回调函数,否则返回失败回调函数。与其说是承诺,这里我更倾向它是先知,它可以预先将未来发生的事,你无需等待最终结果出来,可以继续规划你代码的走向,比如说我们要败魔王,就要先干掉魔王身边的小弟,而你只需要规划好先打小弟,在打魔王就行了。

Promise

首先我们来看看这个东西到底怎么用

new Promise( function(resolve, reject) {...}

先看它的语法,它是由new Promise()传入一个带有 resolve 和 reject 两个参数的函数 。
我们调用试一试

new Promise(function(resolve, reject){
   console.log(1);
});

emmmmmm……,直接就console了,感觉没啥用,但是我们知道了在Promise的构造函数执行时,传入那个函数会同步执行。
在看看文档,哦,Promise相当于一个承诺,当发出承诺时只是一个中立的状态,承诺是有失败和成功的,resolve代表成功,调用resolve时就代表这个承诺成功了,调用reject时就代表这个承诺失败了。原来如此,是要调用resolve和reject来触发状态呀,然后通过这个状态的改变来执行Promise实例上的then、catch方法,new Promise().then(成功调用,失败调用),new Promise().then(成功调用).catch(失败捕获),好了继续。

then和catch

new Promise(function(resolve, reject){
    console.log(1);
    resolve('aaa');
 }).then(function(val){
     console.log(val);
 });

嗯,成功的承诺触发了成功的函数了,看看失败的,当然失败的也可以用then的第二个函数调用。

new Promise(function(resolve, reject){
    console.log(1);
    reject('aaa');
 }).then(function(val){
     console.log(val);
 }).catch(function(val){
     console.log(val+'error');
 });

嗯,失败也触发了。这下直观多了,其实上面的catch等同于下面的写法。

new Promise(function(resolve, reject){
    console.log(1);
    reject('aaa');
 }).then(function(val){
     console.log(val);
 }).then(undefined, function(val){
     console.log(val+'error');
 });;

then方法可以返回一个新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法。

var aaa = function(num){
    return new Promise(function(resolve,reject){
        if(num>10){
            resolve(num);
        }else{
            reject('这个小于10'+num)
        }
    })
}

aaa(11).then(function(data1) {
    return aaa(9)
}).catch(function(err){
    console.log(err);
});

promise对象的错误会一直向下抛出,直到被catch所捕获,看下面,也就是说catch先找第一个aaa(9)看看这个是不是失败的承诺,是就打印错误,不是就找第二个aaa(8),后面同理,也就是catch可以捕获它前面的所有Promise实例的错误,都会找到最先出错的那个,然后捕获。

aaa(9).then(function(data1) {
    return aaa(8)
}).catch(function(err){
    console.log(err);   
});

其实总的来说Promise层层回调给简化了,用一个承诺的状态来使需要的回调函数调用,可以采用链式写法,避免了回调函数的层层嵌套。
比如我们想写三个异步执行的事件,a是2秒后执行,b是1秒,c是3秒,同时跑的话完成顺序肯定是b->a->c,但是我想按照a->b->c,按顺序执行,那么肯定是a完成后调用b,b完成后再调用c,三个事件嵌套,用Promise我们可以这样做,是不是更直观。

var a = function(){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve('a');
        },2000);
    })
}
var b = function(){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve('b');
        },1000);
    })
}
var c = function(){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve('c');
        },3000);
    })
}
a().then(function(val){
    console.log(val+'执行');
    return b();
}).then(function(val){
    console.log(val+'执行');
    return c();
}).then(function(val){
    console.log(val+'执行');
});

Promise并行

有时我们想几个异步事件一起执行,但是想一起拿到结果,那么这个结果应该是在最后执行那个事件上完成后获得,但是有些时候我们往往不知道这几个异步事件谁最后完成。Promise.all可以实现这样的功能,说通俗点,就是一群人跑步,当最后一个人跑完了,比赛结束,把每个人跑的结果装在数组里返回。

Promise.all([a(),b(),c()]).then(function(val){
        console.log(val);
})

Promise竞速

几个异步事件一起执行,我们想有个执行完了就返回,还是一群人跑步,当第一个人跑完了,比赛结束,返回第一个人跑的结果,这就叫竞速,可以用这个写超时。

Promise.race([a(),b(),c()]).then(function(val){
        console.log(val);
})

resolve()和reject()

Promise自身也有resolve()和reject()方法,其做用是给定Promise对象一个确切的状态,也就是Promise.resolve()这是一个成功的承诺,只会调用成功的方法,reject()同理,当然这二者是不能共存的,两个都存在的情况下,后面的会把前面的覆盖。

var d = Promise.resolve("succ");
//var d = Promise.reject("err");
d.then(function(val) {
   console.log(val);  //succ
},function(err){
    console.log(err); //err
});

ok,接下来我们该实践一下了,就拿开头的勇者大战魔王吧:

勇者大战魔王

首先我们定义三个关卡,关卡里面有骷髅,守卫,魔王

var ul  = document.querySelector('ul');  //装提示文字
var monster = [];
var skeleton = [];   //骷髅堆,能力随机生成
var elite = [];      //守卫堆,能力随机生成
var devil = [{attack:200,defense:200,life:300}]; //魔王,都是魔王了,能力肯定不是像杂兵一样随机了,攻击200,防守200,生命300

然后我们写一个批量生成杂兵的函数和一个随机函数

//批量生成杂兵
function generate(arr,num,min,max,life,experience){    //装杂兵的数组,杂兵个数,随机最小值,随机最大值,生命值,经验
    for(var i=0;i<num;i++){
        var random = Rand(min,max);
        arr.push({
            attack:random,
            defense:random,
            life:life,
            experience:experience
        })
    }
    return arr;
}
//随机函数,根据传入的最大和最小值生成二者之间的随机值。 
function Rand(Min,Max){
    var Range = Max - Min;
    var Rand = Math.random();
    return(Min + Math.round(Rand * Range));
}

传入参数生成骷髅和守卫,把这三个关卡装入monster中

skeleton = generate(skeleton,10,2,6,50,10);
elite = generate(elite,3,10,30,100,50);
monster.push(skeleton,elite,devil);

我们创建一个勇者,勇者有他的面板attribute和他的状态state以及他的一系列的经历函数request

var brave = {
    attribute:{
        attack:10,
        defense:4,
        life:200,
        experience:0,
        Grade:0,
    },   
    state:function(state){
        var text = `勇者${state},攻击${this.attribute.attack},防御${this.attribute.defense},生命${this.attribute.life},等级${this.attribute.Grade}`;
        addli(text);    //页面提示文字生成
    },
    request:function(obj){
        var monster = obj.monster;
        for(var i in monster){
            var kill;                 //需要攻击怪物次数
            if(Math.random()>0.4){    //40%概率致命一击
                var sh = this.attribute.attack-monster[i].defense;
                if(sh>0){             //打怪时是否破防
                    kill = Math.ceil(monster[i].life/sh); 
                }else{
                    kill = 10000;
                }
            }else{
                var sh = this.attribute.attack*2-monster[i].defense;
                if(sh>0){
                    kill = Math.ceil(monster[i].life/sh); 
                }else{
                    kill = 10000;
                }
            }
            var Injured = monster[i].attack-this.attribute.defense;   //受到怪物伤害
            if(kill===10000){
                this.attribute.life = 0;                              //打怪不破防生命直接清0
            }else{
                this.attribute.life =  Injured>0?this.attribute.life-(kill*Injured):this.attribute.life;    //打怪后剩余生命
            }
            if(this.attribute.life>0){                  //存活
                this.attribute.experience+=monster[i].experience;   //经验++
                if(this.attribute.experience==50){                  //经验50升级,升级后属性提升,有概率大幅度提升
                    Math.random()>0.4?this.attribute.attack = this.attribute.attack*2:this.attribute.attack = this.attribute.attack*3;
                    Math.random()>0.4?this.attribute.defense = this.attribute.defense*2:this.attribute.defense = this.attribute.defense*3;
                    this.attribute.life = this.attribute.life+50;
                    this.attribute.Grade = this.attribute.Grade+1;
                    this.state('升级');
                }
                if(this.attribute.experience==150){         //经验150升级
                        Math.random()>0.3?this.attribute.attack = this.attribute.attack*2:this.attribute.attack = this.attribute.attack*4;
                        Math.random()>0.3?this.attribute.defense = this.attribute.defense*2:this.attribute.defense = this.attribute.defense*4;
                        this.attribute.life = this.attribute.life+100;
                        this.attribute.Grade = this.attribute.Grade+1;
                        this.state('升级');
                    }
                if(this.attribute.experience==250){         //经验250升级
                            Math.random()>0.2?this.attribute.attack = this.attribute.attack*2:this.attribute.attack = this.attribute.attack*5;
                            Math.random()>0.2?this.attribute.defense = this.attribute.defense*2:this.attribute.defense = this.attribute.defense*5;
                            this.attribute.life = this.attribute.life+150;
                            this.attribute.Grade = this.attribute.Grade+1; 
                            this.state('升级');
                    }
                obj.success(this.state.bind(this));        //击杀成功后显示勇者状态
            }else{
                obj.error(this.state.bind(this));          //生命归0后显示勇者状态
                return;
            }
        }
    }
}

我们用Promise传入怪物名称,怪物属性,打赢后的回调函数,失败后的回调函数。

var Raiders = function(arr,name){
    return new Promise(function(resolve,reject){ 
            brave.request({
                name:name,
                monster:arr,
                success:function(fn){
                    var text = `攻略${this.name}成功`;
                    addli(text);
                    fn('状态');
                    resolve();
                },
                error:function(fn){
                    fn('状态');
                    reject(this.name);
                }
            })
    })
}
//生成提示文字
function addli(text){
    var li = document.createElement('li');
    li.innerText = text;
    ul.appendChild(li);
    console.log(text);
}

ok,万事具备了,勇者开始进攻:

 <button onclick="aaa()">进攻</button>

function aaa(){
    ul.innerHTML = '';
    brave.attribute = {
        attack:10,
        defense:4,
        life:200,
        experience:0,
        Grade:0,
    };
    Raiders(monster[0],'骷髅').then(function(){
    return Raiders(monster[1],'守卫');
}).then(function(){
    return Raiders(monster[2],'魔王');
}).then(function(){
    var text = `成功击败魔王`;
    addli(text);
}).catch(function(name){
    var text =  `攻略${name}失败,请从新来过`
    addli(text);
})
}

好了,是不是挺有意思的,用了Promise感觉所有的事情都清晰化了,没有那么多复杂的函数嵌套了。

本文示例:链接
本文代码地址:链接

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注