一个星期没更新了,先来个小游戏来放松放松,勇者大战魔王:
考验人品的时候到了,反复进攻,看你能几次进攻打败魔王。
上面只是个小例子,如何实现上面的效果呢,这就要说到我们今天的正主了–Promise,Promise译为承诺。在ES6发布时Promise被ES6列为正式规范,成为最重要的特性之一,Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。简单来说就是把一个函数放进去,符合你定的规范,就返回成功回调函数,否则返回失败回调函数。与其说是承诺,这里我更倾向它是先知,它可以预先将未来发生的事,你无需等待最终结果出来,可以继续规划你代码的走向,比如说我们要败魔王,就要先干掉魔王身边的小弟,而你只需要规划好先打小弟,在打魔王就行了。
Promise
首先我们来看看这个东西到底怎么用
1
| new Promise( function(resolve, reject) {...}
|
先看它的语法,它是由new Promise()传入一个带有 resolve 和 reject 两个参数的函数 。
我们调用试一试
1 2 3
| 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
1 2 3 4 5 6
| new Promise(function(resolve, reject){ console.log(1); resolve('aaa'); }).then(function(val){ console.log(val); });
|
嗯,成功的承诺触发了成功的函数了,看看失败的,当然失败的也可以用then的第二个函数调用。
1 2 3 4 5 6 7 8
| new Promise(function(resolve, reject){ console.log(1); reject('aaa'); }).then(function(val){ console.log(val); }).catch(function(val){ console.log(val+'error'); });
|
嗯,失败也触发了。这下直观多了,其实上面的catch等同于下面的写法。
1 2 3 4 5 6 7 8
| 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方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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实例的错误,都会找到最先出错的那个,然后捕获。
1 2 3 4 5
| 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我们可以这样做,是不是更直观。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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可以实现这样的功能,说通俗点,就是一群人跑步,当最后一个人跑完了,比赛结束,把每个人跑的结果装在数组里返回。
1 2 3
| Promise.all([a(),b(),c()]).then(function(val){ console.log(val); })
|
Promise竞速
几个异步事件一起执行,我们想有个执行完了就返回,还是一群人跑步,当第一个人跑完了,比赛结束,返回第一个人跑的结果,这就叫竞速,可以用这个写超时。
1 2 3
| Promise.race([a(),b(),c()]).then(function(val){ console.log(val); })
|
resolve()和reject()
Promise自身也有resolve()和reject()方法,其做用是给定Promise对象一个确切的状态,也就是Promise.resolve()这是一个成功的承诺,只会调用成功的方法,reject()同理,当然这二者是不能共存的,两个都存在的情况下,后面的会把前面的覆盖。
1 2 3 4 5 6 7
| var d = Promise.resolve("succ"); d.then(function(val) { console.log(val); },function(err){ console.log(err); });
|
ok,接下来我们该实践一下了,就拿开头的勇者大战魔王吧:
勇者大战魔王
首先我们定义三个关卡,关卡里面有骷髅,守卫,魔王
1 2 3 4 5
| var ul = document.querySelector('ul'); var monster = []; var skeleton = []; var elite = []; var devil = [{attack:200,defense:200,life:300}];
|
然后我们写一个批量生成杂兵的函数和一个随机函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| //批量生成杂兵 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中
1 2 3
| skeleton = generate(skeleton,10,2,6,50,10); elite = generate(elite,3,10,30,100,50); monster.push(skeleton,elite,devil);
|
我们创建一个勇者,勇者有他的面板attribute和他的状态state以及他的一系列的经历函数request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| 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){ 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; }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){ 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){ 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){ 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)); return; } } } }
|
我们用Promise传入怪物名称,怪物属性,打赢后的回调函数,失败后的回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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,万事具备了,勇者开始进攻:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <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感觉所有的事情都清晰化了,没有那么多复杂的函数嵌套了。
本文代码地址:链接