Исключение переброски в NodeJS и отсутствие потери трассировки стека

Как я могу переустановить ошибку или исключение в nodejs/javascript и включить специальное сообщение.

У меня есть следующий код

var json = JSON.parse(result);

и я хотел включить контент result в сообщение об исключении, если возникает ошибка синтаксического анализа. Что-то вроде этого.

1.  try {
2.    var json = JSON.parse(result);
3.    expect(json.messages.length).to.be(1);
4.  } catch(ex) {
5.    throw new Error(ex.message + ". " + "JSON response: " + result);
6.  }

Проблема в том, что я теряю трассировку стека.

Есть ли способ сделать это аналогично java?

throw new Error("JSON response: " + result, ex);

Ответ 1

Я не знаю такого нативного метода, как Java, и я пока не нашел элегантного решения для упаковки ошибок.

Проблема с созданием new Error заключается в том, что вы можете потерять метаданные, которые были прикреплены к исходной Error которая была выдана, стек и тип, как правило, элементы теряются.

Внесение изменений в существующую сгенерированную ошибку происходит быстрее, но все же можно изменить данные об ошибке в случае ее отсутствия. Также кажется неправильным возиться с ошибкой, которая была создана где-то еще.

Создать новую ошибку и новый стек

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

Когда исходная сгенерированная ошибка и ее обработчик находятся в разных местах или файлах (что часто встречается в обещаниях), вы можете отследить источник исходной ошибки, но не отследить обработчик, в котором ошибка была фактически перехвачена. Чтобы избежать этого, полезно хранить в stack некоторые ссылки как на исходную, так и на новую ошибку. Также полезно иметь доступ к полной исходной ошибке, если в ней хранятся дополнительные метаданные.

Вот пример перехвата ошибки, ее переноса в новую ошибку, но с добавлением исходного stack и сохранением error:

try {
  throw new Error('First one')
} catch (error) {
  let e = new Error('Rethrowing the "${error.message}" error')
  e.original = error
  e.stack = e.stack.split('\n').slice(0,2).join('\n') + '\n' +
            error.stack
  throw e
}

Который бросает:

/so/42754270/test.js:9
    throw e
    ^

Error: Rethrowing the "First one" error
    at test (/so/42754270/test.js:5:13)
Error: First one
    at test (/so/42754270/test.js:3:11)
    at Object.<anonymous> (/so/42754270/test.js:13:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)

Итак, мы создали новую общую Error. К сожалению, тип исходной ошибки становится скрытым из выходных данных, но error была прикреплена как .original так что к ней все еще можно получить доступ. Новый stack был в значительной степени удален, за исключением важной строки генерации и добавления исходного stack ошибок.

Любые инструменты, которые пытаются проанализировать трассировки стека, могут не работать с этим изменением или в лучшем случае, они обнаруживают две ошибки.

Классы ошибок ES2015

Превращаем это в повторно используемый класс ошибок ES2015+...

// Standard error extender from @deployable/errors

class ExtendedError extends Error {
  constructor(message){
    super(message)
    this.name = this.constructor.name
    this.message = message
    if (typeof Error.captureStackTrace === 'function'){
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class RethrownError extends ExtendedError {
  constructor(message, error){
    super(message)
    if (!error) throw new Error('RethrownError requires a message and error')
    this.original = error
    this.new_stack = this.stack
    let message_lines =  (this.message.match(/\n/g)||[]).length + 1
    this.stack = this.stack.split('\n').slice(0, message_lines+1).join('\n') + '\n' +
                 error.stack
  }
}

throw new RethrownError('Oh no a "${error.message}" error', error)

Результаты в

/so/42754270/test2.js:31
    throw new RethrownError('Oh no a "${error.message}"" error', error)
    ^

RethrownError: Oh no a "First one" error
    at test (/so/42754270/test2.js:31:11)
Error: First one
    at test (/so/42754270/test2.js:29:11)
    at Object.<anonymous> (/so/42754270/test2.js:35:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)

Затем вы знаете, что всякий раз, когда вы видите RethrownError, исходная ошибка все равно будет доступна на .original.

Этот метод не идеален, но это означает, что я могу перепечатывать известные ошибки из базовых модулей в универсальные типы, которые легче обрабатывать, обычно с отфильтрованным уловом .catch(TypeError, handler) птиц .catch(TypeError, handler)

Та же ошибка с измененным стеком

Иногда вам нужно сохранить исходную ошибку в основном как есть.

В этом случае вы можете просто добавить/вставить новую информацию в существующий стек.

file = '/home/jim/plumbers'
try {
   JSON.parse('k')
} catch (e) {
   let message = 'JSON parse error in ${file}'
   let stack = new Error(message).stack
   e.stack = e.stack + '\nFrom previous ' + stack.split('\n').slice(0,2).join('\n') + '\n'
   throw e
}

Который возвращается

/so/42754270/throw_error_replace_stack.js:13
       throw e
       ^

SyntaxError: Unexpected token k in JSON at position 0
    at Object.parse (native)
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:8:13)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
From previous Error: JSON parse error in "/home/jim/plumbers"
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:11:20)

Также обратите внимание, что обработка стека проста и предполагает, что сообщение об ошибке состоит из одной строки. Если вы сталкиваетесь с многострочными сообщениями об ошибках, вам может понадобиться поискать \n at для завершения сообщения.

Ответ 2

Если все, что вы хотите сделать, это изменить сообщение, вы можете просто изменить сообщение:

try {
  throw new Error("Original Error");
} catch(err) {
  err.message = "Here is some context -- " + err.message;
  throw err;
}

Ответ 3

вы также можете просто перебросить ошибку в цепочку попыток. Если вы хотите изменить что-либо, сделайте это по пути: перед выражением throw в b.

function a() {
    throw new Error('my message');
}

function b() {
    try {
        a();
    } catch (e) {
        // add / modify properties here
        throw e;
    }
}

function c() {
    try {
        b();
    } catch (e) {
        console.log(e);
        document.getElementById('logger').innerHTML = e.stack;
    }
}
c();
<pre id="logger"></pre>