初探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感觉所有的事情都清晰化了,没有那么多复杂的函数嵌套了。
近期评论