Как и 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.