Страницы: 1
Заранее приношу свои извинения за такое вот название темы - не смог подобрать адекватного русскоязычного определения тому, о чем речь пойдет ниже (варианты типа "ловля ошибок верхнего уровня", по-моему, как минимум неблагозвучны ).
Преамбула. Расширение работает с sqlite-базой, причем работа ведется в main GUI thread. Согласно требованиям к оптимизации, в этом случае все CRUD-операции должны выполняться асинхронно (требование озвучено, например, здесь). Далее, бизнес-логика работы с базой такова, что в контексте одной бизнес-операции выполняется несколько CRUD-операций с промежуточной обработкой результатов от предыдущего вызова. Я так и не смог найти способа организовать отдельную нить выполнения (нашел вот это, что мне не подходит ввиду накладываемых ограничений на потокобезопасность используемых компонентов, и необходимость постоянного вызова processNextEvent для обеспечения отсутствия блокировки главного потока - и опять же, так делать не рекомендуется) - иначе я бы просто организовал работу с базой в виде синхронных вызовов, и в отдельную нить отправлял бы вызов всей бизнес-операции целиком.
Ввиду всего вышеизложенного, работа была организована во вложенных коллбэках асинхронных методов - примерно так (для простоты в примере используется nsITimer - суть от этого не меняется). Минус приведенного кода состоит в том, что если где-то внутри цепочки вызовов возникнет исключение, то без try/catch для каждого вложенного вызова исключение просто уйдет в top-level, и о нём никто никогда не узнает - try/catch вокруг вызова самого верхнего уровня возникшего исключения не видит (в примере по данной выше ссылке можно любой logStringMessage заменить, например, на foo - и, не считая отсутствия результата выполнения кода, ничего не произойдет). Использование же try/catch на каждом уровне вложенности и без того "лестничного" кода еще больше ухудшает читаемость кода. Т.е. в идеале хотелось бы иметь в распоряжении некий exception handler самого верхнего уровня, который позволял бы отлавливать исключения, возникающие в описанной ситуации. Такого хэндлера я, опять же, не нашел. Поэтому пока сделал вот таким образом - при возникновении исключения в цепочке вызвов (смоделировать можно, как и в предыдущем случае, заменой logStringMessage на foo) исключение нормально доходит до верхнего уровня. Хотя фактически и имеет место быть всё тот же try/catch на каждом уровне, но в явном виде в него вызовы не завернуты, читаемость кода, насколько это вообще возможно в данной ситуации, сохранена, ошибки отлавливаются. Но всё же хотелось бы знать, нет ли какого-либо штатного объекта/метода/приёма для обработки исключений в описанной ситуации (и не изобрёл ли я велосипед?).
P.S. В приведенных примерах кода на pastebin не хватает импорта
на верхнем уровне - по ошибке скопипастил без него.
Отредактировано hydrolizer (17-04-2011 10:53:45)
Отсутствует
Сейчас в связи с одним вопросом, не связанным напрямую с вопросом данной темы, заглянул в код 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, заворачиваются в вызов приведенного метода - т.е., видимо, всё же этого глобально-верхоуровнего хэндлера исключений в природе не существует, и оборачивание асинхронных вызовов в код, подобный приведенному - стандартная практика.
Отсутствует
В результате рефакторинга существующего кода пришел вот к такому решению:
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 (точку вызова).
Ну, и к чему всё это: просьба покритиковать. Вроде бы всё работает как надо, но мало ли - не учел чего-то очевидного, и в случае, отличном от моего частного, словлю хорошо если ошибку - вывод их на верхний уровень вызова и был целью. Хуже, если возникнет ошибка, прошедшая мимо стека вызовов - о которой никогда никто не узнает.
Отсутствует
Страницы: 1