Полезная информация

Пользователи не любят читать документацию. Станьте оригинальным, будьте не как все. Ознакомьтесь с нашей базой знаний.

№117-04-2011 10:26:53

hydrolizer
Участник
 
Группа: Extensions
Зарегистрирован: 22-07-2009
Сообщений: 1945
UA: Firefox 4.0

Javascript: top-level exception handling

Заранее приношу свои извинения за такое вот название темы - не смог подобрать адекватного русскоязычного определения тому, о чем речь пойдет ниже (варианты типа "ловля ошибок верхнего уровня", по-моему, как минимум неблагозвучны :) ).
Преамбула. Расширение работает с 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 не хватает импорта

Выделить код

Код:

Components.utils.import("resource://gre/modules/Services.jsm");

на верхнем уровне - по ошибке скопипастил без него.

Отредактировано hydrolizer (17-04-2011 10:53:45)

Отсутствует

 

№217-04-2011 22:40:46

hydrolizer
Участник
 
Группа: Extensions
Зарегистрирован: 22-07-2009
Сообщений: 1945
UA: Firefox 4.0

Re: Javascript: top-level exception handling

Сейчас в связи с одним вопросом, не связанным напрямую с вопросом данной темы, заглянул в код 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, заворачиваются в вызов приведенного метода - т.е., видимо, всё же этого глобально-верхоуровнего хэндлера исключений в природе не существует, и оборачивание асинхронных вызовов в код, подобный приведенному - стандартная практика.

Отсутствует

 

№313-05-2012 22:48:14

hydrolizer
Участник
 
Группа: Extensions
Зарегистрирован: 22-07-2009
Сообщений: 1945
UA: Firefox 13.0

Re: Javascript: top-level exception handling

В результате рефакторинга существующего кода пришел вот к такому решению:

Выделить код

Код:

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 (точку вызова).
Ну, и к чему всё это: просьба покритиковать. Вроде бы всё работает как надо, но мало ли - не учел чего-то очевидного, и в случае, отличном от моего частного, словлю хорошо если ошибку - вывод их на верхний уровень вызова и был целью. Хуже, если возникнет ошибка, прошедшая мимо стека вызовов - о которой никогда никто не узнает.

Отсутствует

 

Board footer

Powered by PunBB
Modified by Mozilla Russia
Copyright © 2004–2020 Mozilla Russia GitHub mark
Язык отображения форума: [Русский] [English]