用户:
芜湖咸鱼查看:12 回复:18 评论:12 创建时间:2021-03-02T21:54:24
众所周知,JS是事件驱动的,很多地方都会用到回调函数(比如world.onTick)
如果你是从Kitten或者Box2.0来的,你可能会想,怎么找不到"等待[ 1 ]秒"的代码呢?
其实是有的,只不过我相信官方不会很推荐这种做法
这个函数就是setTimeout(cb: Function, timeMS: number)
这个函数的作用就是在指定时间(以毫秒为单位)后执行指定的函数,对于简单的等待来说确实很方便,但是如果要等一段时间,干一件事,再等一段时间,干第二件事.... 以此类推,回调就会越套越多,比如这样:
setTimeout(()=>{
// do something...
setTimeout(()=>{
// do someting...
setTimeout(()=>{
// do someting...
},1000)
},1000)
},1000)
这样就叫做"回调地狱",因为不仅写着很麻烦,而且弄错一个括号就会造成很难修的bug
而这时就有一种优雅的写法,叫做Promise,这个东西可以形成一个阻塞,并且可以控制何时结束阻塞,这就能让程序在某处暂停一些时间
而执行这个阻塞就要使用await关键字,后面加上一个Promise对象(或是返回Promise的函数),并且还要注意在await最近的函数前要加上async,让这个函数变为异步函数,否则直接使用await是会报错的~
具体深入Promise来看,它的基本用法大致是这样new Promise(cb: r=> any);
其中传入的回调函数接受一个参数,这个参数也是一个函数,当这个名为r的函数被调用的时候,阻塞就会结束,并在await处返回r的第一个参数值,所以如果要做一个"等待 [1] 秒",就可以这样实现
function wait(ms){
return new Promise((r)=>{setTimeout(r,ms)});
}
// 使用示例
world.onPlayerJoin(async ()=>{
await wait(1000)
world.say("一秒过去了")
})
对于wait函数的定义也可以简写成这样
const wait=ms=>new Promise(r=>setTimeout(r,ms));
鉴于这个功能的实用性,Box3内置了sleep函数,用法和功能和上面的例子相同
其实Promise的功能远不止于此
下面来看一个例子:玩家足够5人后提示开始游戏
喵sync的做法
let players = 0;
world.onPlayerJoin(()=>{players++});
let tickToken = world.onTick(()=>{
if(players>5){
tickToken.cancel(); // 用来取消onTick监听
world.say("开始游戏")
}
})
有async的做法
(async ()=>{
for(x=0;x<5;){
await world.nextPlayerJoin();
}
world.say("游戏开始")
})()
对比两种写法,显然第二种更为简洁,这就是async的一个优点"结构化处理事件",这样你就可以更简单地控制逻辑结构,避免专注于事件处理,这对于有游戏规则和游戏计时的代码非常有用
但是使用async也有需要注意的地方,async函数会在主进程中立即执行,也就是说上下并列的两个async函数会一起执行,比如下面这个例子
(async()=>{
for(x=0;x<5;x++){await sleep(100);console.log("hi")}
})(); // 这里必须有分号
(async()=>{
for(x=0;x<5;x++){await sleep(100);console.log("hello")}
})();
乍眼看去应该是先有几个hi,再有几个hello,可是实际运行起来却是hi和hello同时出现,这是因为这两个async函数是同时执行的,而每到await的时候,程序会暂停当前代码块的执行,去处理其他代码块(异步执行),所以会造成这样的结果
再看这个例子
(async()=>{ for(x=0;x<5;){} })();
(async()=>{ for(x=0;x<5;x++){console.log("hello")} })();
正常来讲,两个async一起执行,第一个没有输出,第二个应该会看到5个hello,但是运次许久都看不见hello,最后还会因为运行超时而被阻断。这是因为,只有在程序空闲的时候才会处理其他代码,而在第一个for循环中,因为x永远不能大于5,所以会卡住,导致程序"太忙",就不能处理其他代码了。所以大家在async中有耗时较长的运算时,尽量在适当的地方加上await sleep(1); 甚至 0.1,这样可以让其他代码执行,并且对运算效率的影响也不是很大。
这个高级技巧,你get了吗~