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