对于之前用其他语言从事游戏开发的开发者而言,JavaScript迷人的地方在于”异步”。用Java或C++进行开发,(程序)通常是按照书写的顺序执行。但是使用JavaScript以及MobageSDK的API进行开发的时候,程序的执行顺序与书写顺序不一致的情况很常见。 如果不能熟练的掌握异步编程的相应技巧,就经常会碰到诸如“想要读取数据的时候却怎么也读不到”,“信息收集不全”,“无法等到所有操作都完成之后再执行指定操作”等等问题。这里简单介绍一下相应的解决方法
本文可能介绍的不够全面,可以在这里 jsDeferred 找到更加详细的说明
下面的函数执行结果会是怎么样的呢?
var showCounter() { var counter = 1; setInterval(function() { text.setText("Counter: " + counter); counter += 1; }, 1000);
执行上述代码之后,变量初始化,设定timer之后,(程序)会立即结束。 setInterval 当中的函数,在程序结束之后过一段时间才会被调用执行。在那个时候对 showCounter 的调用已经结束了(因为是内部的引用变量,所以 counter 还存在)
将回调函数中可能发生的异常捕获,输出到log中
var showCounter() { try { var counter = 1; setInterval(function() { text.setText("Counter: " + counter); counter += 1; }, 1000); } catch(e) { console.log(e); }
上边的代码不能捕获异常
var showCounter() { var counter = 1; setInterval(function() { try { text.setText("Counter: " + counter); counter += 1; } catch(e) { console.log(e); } }, 1000);
理由如上所述,回调函数被调用的时候被视为完全不同的函数。通常的函数被调用时如果发生异常,则沿着调用栈一层层向上弹出。如果遇到catch,则被捕获进行相应的处理。如上的回调函数没有调用其他函数,在执行过程中即使发生异常,也会因为调用这个回调函数的caller是完全不同的函数,所以也就不能(在原函数中的catch中)捕获回调函数中发生的异常
异步执行的优点
因为是按照顺序调用各部分的代码执行,如文件读写或者网络通信等比较慢的操作也可以不用做太多处理简单的实现
缺点有如下几点
异步执行的时候,不会自动的得到来自执行完成的回调函数的通知。因此需要自己实现类似与消息通信的机制。结果本来是并发执行的代码,到头来变成了顺序执行,反而降低了效率
在mobage-SDK中,虽然在同时使用网络通信,文件读写的异步API以及画面绘制的Emitter的情况下非常需要一种异步的队列逻辑(大概是将异步操作序列化用——译注),但是现在作为开发者而言无法自己实现这种逻辑
将异步执行的函数同步可以使用如下做法
NG例
var ConfigLoader = Core.Class.subclass({ loadFile: function(filename) { Storage.readFile("config.txt", function(err) { this.map = content; }); }});
如果写成如上这样的代码,即使配置文件可以成功的被读入,接下来所做的操作也只能估算一个大概的读取时间等待之后再执行
如果想要在函数内部使用异步的API,必须定义其中的回调函数。mobage SDK中API,如果是异步执行的函数全部都可以接受回调函数作为参数
OK例
var ConfigLoader = Core.Class.subclass({ loadFile: function(filename, callback) { Storage.readFile("config.txt", function(err) { this.map = content; if (callback) { callback(); } }); }});
当然,读取是否成功的flag以及已经读取到的内容会作为参数被传递到回调函数中。而且可以针对成功和失败指定不同的回调函数
在多线程程序中,可以通过使用队列或者线程合并等方法,实现进程中各线程的整合(Mobage上的游戏有很多地方看上去像是并发进行的,但实际上是个单线程程序——译注)
比如说想要同时进行标题画面的展现,和下载。(标题画面使用 Dn.VFX ,带有fade in、fade out效果 )
要开始游戏必须首先进行下载。此外,全部已经下载完成(不再需要下载)的时候也需要先展现一段时间的标题画面。上述的两个操作可以完全并发的执行
// 动画化的标题画面var titleImage = GL2.Sprite();titleImage.setImage(imageFile, [240, 320]);VFX.enchant(titleImage).fi(3).wait(3).fo(1).end();// 下载var downloader = new Network.DownloadManifest();dl.start( url, pass, filename, onProgress, onFinish);
无论是画面绘制还是下载都是异步执行,因此上边的代码本身很快就被执行完。之后才开始实际的操作,在这里没有必要等待两边执行完毕
使用 Dn.Sync 类,可以实现对多重异步处理的同步化
// 等待var sync = new Sync();// 动画化的标题画面var titleImage = GL2.Sprite();titleImage.setImage(imageFile, [240, 320]);VFX.enchant(titleImage).fi(3).wait(3).fo(1).and(sync.job(), []).end();// 下载var downloader = new Network.DownloadManifest();dl.start( url, pass, filename, onProgress, sync.job(function() {onFinish()}));sync.wait(function() { // 游戏的主逻辑});
sync.job() 会返回一个函数对象。当各自的任务执行完成时,会调用这个函数
sync.wait() 函数会在所有 sync.job() 中的函数对象被执行后被调用。将代码写成这样便可以实现下载和标题画面的绘制完成之后调用其他函数等操作
如下面代码所示,数组中存储的异步函数(最后结束之后接受回调函数)被一个个的执行,便可实现回调函数的连锁(序列化)调用
function create_chain(async_func, cb) { return function() { async_func(cb); }}
var cb = function() { // 想要在最后才执行的部分};for (var i=functions.length-1; i >= 0; -- i) { cb = create_chain(functions[i], cb);}cb();
将代码写成如上形式,可以让回调函数一个个的顺序执行
联系客服