Статьи

Node.js для программистов PHP # 3: исключения и ошибки

Как и PHP, JavaScript поддерживает исключения — только они называются ошибками. Однако из-за асинхронного характера Node.js классическая стратегия try / catch не работает. Чтобы ловить асинхронные ошибки, Node настоятельно рекомендует использовать особую сигнатуру для функций обратного вызова. Я объясню все это в ближайшее время, но теперь я должен сделать признание.

Digest3

Иногда все идет не так

Я не ем мясо. Это не потому, что я слишком люблю животных — я не верю, что у кур есть душа. Просто я ел много мяса ранее в своей жизни, и если каждый человек на этой планете съест столько же мяса, сколько я, место станет ужасным, чтобы жить. Если ты мне не веришь, иди посмотри это видео на YouTube и поблагодарить профессора Альберта Бартлетта за депрессию. Итак, я перестал есть мясо год назад, и до сих пор мои зубы не падали или что-то еще.

Но, честно говоря, я все еще ем мясо время от времени. Видите ли, вегетарианцы должны принимать таблетки, чтобы компенсировать питательные вещества, которые они не получают из мяса. Я предпочитаю есть мясо, чем таблетки. А поскольку воскресная трапеза часто является семейным праздником, а поскольку мясо ДОЛЖНО быть в меню, я ем мясо почти каждое воскресенье.

И я люблю мясо. Это значит, что я люблю воскресные блюда, будь то тушеная телятина с белым вином или жареная курица с картофелем фри. И в прошлый раз я съел слишком много. Тогда все пошло не так.

Пищеварительная система PHP

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

<?php
class HumanBody
{
  const DIGESTIVE_CAPACITY = 10;
  protected $totalNutriments = 0;

  public function eat($meal)
  {
    try {
      foreach ($meal as $element) {
        $this->digest($element);
      }
      return true;
    } catch (Exception $e) {
      $this->totalNutriments = 0;
      $this->vomit($meal);
      return false;
    }
  }

  public function digest($element)
  {
    // actual digestion taking place here
    // transforming $element into $nutriments
    $this->totalNutriments += $nutriments;
    if ($this->totalNutriments > self::DIGESTIVE_CAPACITY) {
      throw new Exception('Cannot digest: You ate too much');
    }
    return $nutriments;
  }

  public function vomit($meal)
  {
    // you get the idea
  }
}

Пищеварительная система Node.js

JavaScript — еще один хороший способ выразить мои страдания. А поскольку Node.js ставит дозу асинхронности поверх JavaScript , это хорошая возможность показать, что переваривание может занять время. Вот эквивалент предыдущего класса, написанного для Node.js:

function HumanBody() {
  this.digestiveCapacity = 10;
  this.totalNutriments = 0;
};

HumanBody.prototype.eat = function(meal, callback) {
  var body = this;
  var processedElements = 0;
  try {
    meal.forEach(function(element) {
      body.digest(element, function() {
        processedElements++;
        if (processedElements == meal.length) {
          callback(true);
        }
      });
    });
  } catch (err) {
    this.totalNutriments = 0;
    this.vomit(meal);
    callback(false);
  }
}

HumanBody.prototype.digest = function(element, callback) {
  setTimeout(function() {
    // actual asynchronous digestion taking place here
    // transforming element into nutriments
    this.totalNutriments += nutriments;
    if (this.totalNutriments > this.DIGESTIVE_CAPACITY) {
      throw 'Cannot digest: You ate too much';
    }
    callback(nutriments);
  }, 100);
}

HumanBody.prototype.vomit = function(meal) {
  // you get the idea
}

В digest(), setTimeout()есть ли для симуляции асинхронной обработки. Асинхронные функции не используют returnоператор для возврата своего результата. Вместо этого они вызывают данный обратный вызов с результатом в качестве параметра. Вот почему digest()заканчивается, callback(nutriments)а не return $nutrimentsкак в PHP. Поэтому eat()должен обеспечить обратный вызов при звонке digest(). Когда все элементы обработаны, eat()можно вернуться true, вызвав свой собственный обратный вызов.

Подсказка : Если вы не понимаете, зачем var body = thisнужна эта строка , вы еще не сталкивались с первой проблемой JavaScript. Загляните в мою статью об этом для более подробной информации.

Второй совет : тест if (processedElements == meal.length)— это способ определить, когда асинхронное переваривание пищи завершено. Необходимость повторной синхронизации асинхронной обработки возникает довольно часто в Node.js, и вместо использования стандартного кода, подобного этому, вы обязательно должны использовать asyncмодуль . Предлагает ресинхронизацию и многое другое. На мой взгляд, это должно быть частью ядра Node.js.

Подождите, это не работает

digest()Функция асинхронная, помните? Поэтому, когда Node выполняет этот кусок кода из eat():

try {
  meal.forEach(function(element) {
    body.digest(element, function() {
      processedElements++;
      if (processedElements == meal.length) {
        callback(true);
      }
    });
  });
} catch (err) {
  this.totalNutriments = 0;
  this.vomit(meal);
  callback(false);
}

Узел перебирает все элементы в трапезе, выполняет HumanBody.digest()все из них и затем завершает работу. Это означает, что если проблема с пищеварением возникает во время digest(), это произойдет после окончания eat()выполнения, а это означает, что после завершения try {} catch {}оператора. ErrorНикогда не поймают, и это драматично. Представьте себе, что человеческое тело не может рвать, даже когда оно переполнено? Взрыв!

Не пытайтесь / ловите в Node.js

В асинхронном программировании канал связи между функцией вызывающего абонента и вызываемым абонентом является обратным вызовом. Единственное решение для преодоления неперехваченной ошибки — передача Errorобратного вызова. Таким образом, eat()и digest()функции должны быть переработаны следующим образом :

function HumanBody() {
  this.digestiveCapacity = 10;
  this.totalNutriments = 0;
};

HumanBody.prototype.eat = function(meal, callback) {
  var body = this;
  var processedElements = 0;
  meal.forEach(function(element) {
    body.digest(element, function(error) {
      if (error) {
        this.totalNutriments = 0;
        this.vomit();
        return callback(false);
      }
      processedElements++;
      if (processedElements == meal.length) {
        callback(true);
      }
    });
  });
}

HumanBody.prototype.digest = function(element, callback) {
  setTimeout(function() {
    // actual asynchronous digestion taking place here
    // transforming element into nutriments
    this.totalNutriments += nutriments;
    if (this.totalNutriments > this.DIGESTIVE_CAPACITY) {
      callback(new Error('Cannot digest: You ate too much'), null);
    }
    callback(null, nutriments);
  }, 100);
}

HumanBody.prototype.vomit = function(meal) {
  // you get the idea
}

Важно то, что новая подпись обратного вызова передана digest():

function(error) {
  if (error) {
    // ...
  }
  // ...
}

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

И это единственный способ отловить ошибку, выданную в асинхронной функции. Так что забудьте try {} catch {}в Node.js. Вы будете использовать их очень редко, в то время как обратные вызовы ожидают ошибок повсюду.

Всегда неси свои ошибки с собой

То, что происходит с асинхронным перевариванием, может происходить в каждой асинхронной функции в Node.js. Вот почему основные асинхронные функции всегда отображаются errв качестве первого аргумента каждого обратного вызова. Если операция была успешно завершена, то первым аргументом будет nullили undefined. Вот пример с эквивалентом PHP realpath():

require('fs');
fs.realpath(path, function(err, resolvedPath) {
  if (err) {
    // ..
  }
  // use the resolvedPath
});

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

Вывод

В HumanBodyреализации Node.js все еще есть недостаток . Если во время переваривания пищи происходит что-то плохое, вызывается рвота, и основной обратный вызов вызывается falseкак аргумент. Но forEach()цикл продолжается. Это означает, что обратный вызов может быть вызван второй раз с trueаргументом as. Исправление оставлено в качестве упражнения для читателя.

Что касается моих проблем с пищеварением, они закончились. Но «Однажды укушенный, дважды застенчивый», поэтому я всегда думаю, что со мной может случиться что-то плохое, когда я ем мясо, и я очень осторожен.

Если вы не хотите отказываться от мяса, вы должны быть осторожны. Всегда ожидайте, что может случиться что-то плохое, и вы будете разрабатывать надежные и эффективные программы — даже в асинхронном Node.js.