Почему '.catch(err => console.error(err))' обескуражен?

Я использую обещания и имею код, который выглядит следующим образом:

function getStuff() { 
  return fetchStuff().then(stuff => 
    process(stuff)
  ).catch(err => {
    console.error(err);
  });
}

Или же:

async function getStuff() { 
  try {
    const stuff = await fetchStuff();
    return process(stuff);
  } catch (err) { 
    console.error(err);
  }
}

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

  • Что не так с return ….catch(err => console.error(err))?
  • Я видел много кода, который делает это, почему?
  • Что мне делать вместо этого?

Ответ 1

Почему старый код делает это?

Исторически сложилось так, что более старые (до 2013 года) обещают библиотекам "проглоченные" необработанные обещания, которые вы не обрабатывали самостоятельно. С тех пор это не так.

Что происходит сегодня?

Браузеры и Node.js уже автоматически регистрируют отказы обезвреженных обещаний или имеют поведение для их обработки и автоматически регистрируют их.

Более того - добавлением .catch вы сигнализируете метод, вызывающий возвращаемую функцию undefined:

// undefined if there was an error
getStuff().then(stuff => console.log(stuff)); 

Вопрос, который нужно задать самому себе при написании асинхронного кода, как правило, "что бы сделала синхронная версия кода?":

function calculate() { 
  try {
    const stuff = generateStuff();
    return process(stuff);
  } catch (err) { 
    console.error(err);
    // now it clear that this function is 'swallowing' the error.
  }
}

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

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

Что делать:

Ничего такого. Что красота его - если вы написали:

async function getStuff() { 
  const stuff = await fetchStuff();
  return process(stuff);
}
// or without async/await
const getStuff = fetchStuff().then(process);

Во-первых, вы все равно получите всевозможные ошибки :)

Что делать, если я запускаю старую версию Node.js?

Старые версии Node.js могут не регистрировать ошибки или показывать предупреждение об устаревании. В этих версиях вы можете использовать console.error (или надлежащее протоколирование) глобально:

// or throw to stop on errors
process.on('unhandledRejection', e => console.error(e));

Ответ 2

Что не так с return ….catch(err => console.error(err))?

Он возвращает обещание, которое будет выполнено с undefined после того, как вы обработали ошибку.

Собственно, ловушка ошибок и протоколирование их хорошо в конце цепи обещаний:

function main() {
    const element = document.getElementById("output");
    getStuff().then(result => {
        element.textContent = result;
    }, error => {
        element.textContent = "Sorry";
        element.classList.add("error");
        console.error(error);
    });
    element.textContent = "Fetching…";
}

Однако, если getStuff() делает getStuff() самой ошибки, чтобы регистрировать ее и не делать ничего, чтобы обрабатывать ее, как предоставление разумного результата возврата, это приводит к undefined показу на странице вместо "Извините".

Я видел много кода, который делает это, почему?

Исторически сложилось так, что люди боялись, что обетования не будут устранены нигде, что приведет к их исчезновению вообще - "проглотил" обещание. Поэтому они добавили .catch(console.error) в каждую функцию, чтобы убедиться, что они заметят ошибки в консоли.

Это больше не требуется, поскольку все современные обещания могут обнаружить необработанные обещания и будут предупреждать об опасности на консоли.

Конечно, по-прежнему необходимо (или, по крайней мере, хорошая практика, даже если вы не ожидаете, что что-то не получится), чтобы поймать ошибки в конце цепочки обещаний (когда вы еще не вернете обещание).

Что мне делать вместо этого?

В функциях, которые return обещание их вызывающему абоненту, не регистрируйте ошибки и не проглатывайте их, делая это. Просто верните обещание, чтобы вызывающий мог поймать отклонение и обработать ошибку соответствующим образом (путем регистрации или чего-то еще).

Это также упрощает код:

function getStuff() { 
  return fetchStuff().then(stuff => process(stuff));
}

async function getStuff() { 
  const stuff = await fetchStuff();
  return process(stuff);
}

Если вы настаиваете на том, чтобы что-то делать с причиной отклонения (ведение журнала, изменение информации), обязательно повторите ошибку:

function getStuff() { 
  return fetchStuff().then(stuff =>
    process(stuff)
  ).catch(error => {
    stuffDetails.log(error);
    throw new Error("something happened, see detail log");
  });
}

async function getStuff() {
  try {
    const stuff = await fetchStuff();
    return process(stuff);
  } catch(error) {
    stuffDetails.log(error);
    throw new Error("something happened, see detail log");
  }
}

То же самое, если вы обрабатываете некоторые из ошибок:

function getStuff() { 
  return fetchStuff().then(stuff =>
    process(stuff)
  ).catch(error => {
    if (expected(error))
      return defaultStuff;
    else
      throw error;
  });
}

async function getStuff() {
  try {
    const stuff = await fetchStuff();
    return process(stuff);
  } catch(error) {
    if (expected(error))
      return defaultStuff;
    else
      throw error;
  }
}

Ответ 3

Причина, по которой вы не должны catch ошибки, если не требуется абсолютно (что никогда), заключается в том, что

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

Последствия

  1. Как только ошибка catch обработчиком catch, она считается выполненной и обработанной. Все последовательные подписчики обещаний в цепочке обещаний будут называть их обработчиками успеха вместо отказов или обработчиков catch. Это приводит к странным поведением. Это никогда не предназначенный поток кода.

  2. Если функция на более низком уровне, такая как метод обслуживания (getStuff), обрабатывает ошибки в catch, это нарушает принцип разделения проблем. Ответственность обработчика службы должна быть исключительно для сбора данных. Когда этот вызов данных выходит из строя, приложение, которое вызывает этот обработчик, должно управлять ошибкой.

  3. Ловушка ошибки в некоторой функции, которая попадает другой, приводит к странным поведением вокруг и делает очень трудным отслеживать основные причины ошибок. Чтобы отслеживать такие ошибки, мы должны включить Break on Caught Exceptions в консоли Chrome dev, которая будет ломаться на каждом catch и может занять несколько часов после отладки.

Это всегда хорошая практика, чтобы справиться обещание отбраковки, но мы всегда должны сделать это с помощью failure обработчика над catch обработчик. Обработчик сбоев будет только улавливать Promise rejections и разрешает приложению прерываться, если возникает какая-либо ошибка JS, как это должно быть.

Ответ 4

ошибка слишком универсальна, это уловка, но есть только так много вещей, с которыми операция завершится неудачно, ошибка - это все errorSomethingSpecific дает детализацию

Ответ 5

Самое общее утверждение здесь, которое применяется в языках за пределами javascript, - это не " ловить " ошибку, если вы не планируете " обрабатывать " эту ошибку. Ведение журнала не обрабатывается.

т.е. в целом, лучшая (только?) причина для улова - это обрабатывать/"обрабатывать" ошибку конструктивным способом, позволяющим выполнять код без каких-либо дополнительных проблем. И опять же, строка ведения журнала, вероятно, никогда не достигает этого...