>Форум Mozilla Россия http://forum.mozilla-russia.org/index.php >Разработка http://forum.mozilla-russia.org/viewforum.php?id=18 >Javascript: top-level exception handling http://forum.mozilla-russia.org/viewtopic.php?id=49374 |
hydrolizer > 17-04-2011 10:26:53 |
Заранее приношу свои извинения за такое вот название темы - не смог подобрать адекватного русскоязычного определения тому, о чем речь пойдет ниже (варианты типа "ловля ошибок верхнего уровня", по-моему, как минимум неблагозвучны ). P.S. В приведенных примерах кода на pastebin не хватает импорта на верхнем уровне - по ошибке скопипастил без него. |
hydrolizer > 17-04-2011 22:40:46 |
Сейчас в связи с одним вопросом, не связанным напрямую с вопросом данной темы, заглянул в код AddonManager'а (resource://gre/modules/AddonManager.jsm). Там почти в самом начале есть вот такое: Выделить код Код:function safeCall(aCallback) { var args = Array.slice(arguments, 1); try { aCallback.apply(null, args); } catch (e) { WARN("Exception calling callback", e); } } и вызовы всех callbacks, передаваемых в методы AddonManager, заворачиваются в вызов приведенного метода - т.е., видимо, всё же этого глобально-верхоуровнего хэндлера исключений в природе не существует, и оборачивание асинхронных вызовов в код, подобный приведенному - стандартная практика. |
hydrolizer > 13-05-2012 22:48:14 |
В результате рефакторинга существующего кода пришел вот к такому решению: Выделить код Код:const EXPORTED_SYMBOLS=["AsyncBatch"]; function AsyncBatch(parentBatch, name, onErrorCallback) { if (parentBatch && !(parentBatch instanceof AsyncBatch)) throw new Error(parentBatch+" is not instance of AsyncBatch"); if (!name) throw new Error("Unnamed batches are disallowed."); let _steps = []; let _onError = onErrorCallback; let _name=name; let _isRunning = false Object.defineProperty(this,"steps",{get: function(){ return _steps; }}); Object.defineProperty(this,"step",{get: function(){ return this._step.bind(this); }}); Object.defineProperty(this, "name", {get: function() { return _name; }}); Object.defineProperty(this,"parent", {get: function(){ return parentBatch; }}); Object.defineProperty(this, "onError", {get: function(){ return _onError; }}); Object.defineProperty(this, "isRunning", {get: function(){ return _isRunning; }, set: function(value){ _isRunning = value; }}); } AsyncBatch.prototype = { childBatch: function(name) { return new AsyncBatch(this, name, null); }, toString: function() { let names=[]; let parent=this; while(parent) { names.unshift(parent.name); parent=parent.parent; } return "[AsyncBatch."+names.join(".")+"]"; }, addSteps: function(stepsArray, thisObj) { stepsArray.forEach((function(elem) { this.steps.push({func: elem, thisObj: thisObj}); }).bind(this)); }, safeCall: function(step, args) { try { if (typeof(step.thisObj) == "object") step.func.apply(step.thisObj, args); else step.func(args); } catch(err) { if (typeof(step.thisObj) == "object") Components.utils.reportError(step.thisObj+" on AsyncBatch.safeCall: "+err.message); else Components.utils.reportError("AsyncBatch.safeCall: "+err.message); let parent = this; while(parent) { if (parent.onError) parent.onError.apply(step.thisObj, [err]) parent = parent.parent; } } }, get root() { let parent = this; while(parent.parent) parent = parent.parent; return parent; }, _step: function() { if (!this.isRunning) { if (this.steps.length==0) throw new Error("Empty batches are disallowed."); if (!this.onError && !this.parent) throw new Error("Root batch must have an onError handler."); this.isRunning = true; } if (this.steps.length==0) { if (this.parent) this.parent.step.apply(null, Array.prototype.slice.call(arguments)); return; } this.safeCall(this.steps.shift(), Array.prototype.slice.call(arguments)); }, clear: function() { while (this.steps.length>0) this.steps.pop(); } }; Смысл всех этих манипуляций: конструирование последовательности асинхронных вызовов, в т.ч. вложенных; при возникновении исключения где-то внутри цепочки - передача исключения наверх до точки вызова. Именно этого и не хватало для нормального управления процессом (с нотификацией в случае ошибки). Использование выглядит примерно так: Выделить код Код:let batch = new AsyncBatch(null, "updateToVer2", this.handleError); batch.addSteps([function() { fpcln.backupDB(1, batch.step); }, function(aStatus) { if (!Components.isSuccessCode(aStatus)) throw new Error("dbwrapper.updateToVer2: Database file backup creation error."); this.applyScripts(scripts, batch.step); }, function() { fpcln.getAddonsChrome(batch.step); }, function(chromes) { let stmt=this.buildStatement("update installed_addons set chrome=[:chrome] where ext_id=[:id]", chromes); this.executeStatement(stmt, batch.step); }, function() { ................. }], this); batch.step(); вложенный batch (для примера): Выделить код Код:fixBrokenDRI: function(parentBatch) { var batch = parentBatch.childBatch("fixBrokenDRI"); batch.addSteps([function() { var stmt = this.mDBConn.createStatement("select id, table_name, column_name from v$broken_links"); this.executeStatement(stmt, batch.step); }, function(results) { .................. var stmts=[]; ................... this.executeStatementsArray(stmts, batch.step); }], this); batch.step(); }, передача batch.step как коллбэка гарантирует продвижение вперед по цепочке вызовов (если не возникло исключения); если текущий batch не корневой, то по достижении последнего шага управление передается в родительский batch; при возникновении исключения - по иерархии вложенности исключение пробрасывается в корневой batch (точку вызова). |