Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Обработка ошибок

Чтение и запись файлов, получение данных по сети, выполнение HTTP запросов — всё это операции ввода/вывода. Через них программа взаимодействует с внешней средой. Внешняя среда — штука сложная, с большим количеством разнообразных правил, которые необходимо соблюдать. Например, для успешного чтения файла программа должна иметь к нему доступ. Для записи — свободное место на диске. Для выполнения запросов по сети нужно соединение с сетью. Подобных условий десятки, а то и сотни. Невыполнение хотя бы одного из них приводит к ошибке. Посмотрите на этот впечатляющий список из нескольких сотен всевозможных ошибок.

В JavaScript обработка ошибок работает через механизм исключений. Одни функции их возбуждают, другие обрабатывают через try..catch. Так было в синхронном коде. В асинхронном стандартный механизм уже не работает.

Подумайте над тем как отработает код ниже:

import fs from 'fs';

try {
  // Пытаемся читать директорию, а это ошибка
  fs.readFile('./directory', 'utf-8', () => {
    callUndefinedFunction();
  });
} catch (e) {
  console.log('error!')
}

Так как try/catch работает только с кодом из текущего стека вызовов, то он не сможет перехватить то, что вызвалось в другом стеке. Поэтому мы не увидим сообщения error!, хотя сама ошибка на экране появится:

callUndefinedFunction();
^

ReferenceError: callUndefinedFunction is not defined
    at ReadFileContext.fs.readFile [as callback] (/private/var/tmp/index.js:6:5)

Из вывода видно, что колбек вызвался в своем стеке вызовов, начавшимся внутри функции readFile(). Фактически это означает, что использовать try/catch в асинхронном коде с колбеками — бесполезно, эта конструкция здесь просто неприменима.

Что выведет на экран код ниже?

import fs from 'fs';

try {
  // Пытаемся читать директорию, а это ошибка
  fs.readFile('./directory', 'utf-8', () => {
    console.log('finished!');
  });
} catch (e) {
  console.log('error!');
}

Правильный ответ: finished!. Это кажется странным, учитывая что ошибка возникла внутри функции readFile(), а не в колбеке. Это происходит потому, что содержимое функции readFile() не принадлежит текущему стеку вызовов.

Асинхронные функции всегда имеют дело с внешней средой (операционной системой). Это значит, что любая асинхронная функция потенциально может завершиться с ошибкой. Причём не важно возвращает ли она какие-то данные или нет, ошибка может возникнуть всегда. Именно по этой причине колбеки всех асинхронных функций первым параметром принимают ошибку err и, соответственно, проверять её наличие придётся руками. Если пришёл null, то ошибки нет, если не null — есть. Это очень важное соглашение, которого придерживаются не только разработчики стандартной библиотеки, но и все разработчики сторонних решений.

fs.readFile('./directory', 'utf-8', (err, data) => {
  // Любые ошибки чтения файла: доступ, отсутствие файла, директория вместо файла
  // null неявно приводится к false, поэтому достаточно такой проверки, 
  // любой другой ответ трактуется как true
  if (err) {
    console.log('error!');
    return; // guard expression
  }

  console.log('finished!')
});

В цепочке вызовов придётся делать проверку на каждом уровне:

import fs from 'fs';

fs.readFile('./first', 'utf-8', (error1, data1) => {
  if (error1) {
    console.log('error in first file')
    return;
  }
  fs.readFile('./second', 'utf-8', (error2, data2) => {
    if (error2) {
      console.log('error in second file')
      return;
    }
    fs.writeFile('./new-file', `${data1}${data2}`, (error3, data3) => {
      if (error3) {
        console.log('error during writing')
        return;
      }
      console.log('finished!');
    });
  });
});

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

import fs from 'fs';

const unionFiles = (inputPath1, inputPath2, outputPath, cb) => {
  fs.readFile(inputPath1, 'utf-8', (error1, data1) => {
    if (error1) {
      cb(error1);
      return;
    }
    fs.readFile(inputPath2, 'utf-8', (error2, data2) => {
      if (error2) {
        cb(error2);
        return;
      }
      fs.writeFile(outputPath, `${data1}${data2}`, (error3) => {
        if (error3) {
          cb(error3);
          return;
        }
        cb(null); // не забываем последний успешный вызов
      });
    });
  });
}

Последний вызов можно сократить. Если в самом конце не было ошибки, то вызов cb(error3) отработает так же, как и cb(null), а значит, весь код последнего колбека можно свести к вызову cb(error3):

fs.writeFile(outputPath, `${data1}${data2}`, cb);
// что равносильно fs.writeFile(outputPath, `${data1}${data2}`, error3 => cb(error3));

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу, нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».