10 типичных ошибок Node.js разработчиков (Часть 2)

Часть 2

| Категории: Node.js, Javascript
Eleonora Pavlova


10 mistakes Node.js2


ОШИБКА #6 Отлавливание ошибок внутри колбэков

Как и в большинстве традиционных языков программирования (C++, Java), Javascript поддерживает обработку исключений и отлавливает их с помощью блока «try – catch»:

1
2
3
4
5
6
7
8
9
10
11
12
function slugifyUsername(username) {
if(typeof username === 'string’') {
throw new TypeError('expected a string username, got '+(typeof username))
}
// ...
}
try {
var usernameSlug = slugifyUsername(username);
} catch(e) {
console.log('Oh no!');
}

Но обработка исключений в асинхронном коде не всегда работает гладко. Например, когда вы хотите изолировать большой асинхронно выполняющийся кусок кода в одном «try – catch» блоке:

1
2
3
4
5
6
7
8
9
10
11
12
try {
db.User.get(userId, function(err, user) {
if(err) {
throw err
}
// ...
usernameSlug = slugifyUsername(user.username)
// ...
})
} catch(e) {
console.log('Oh no!')
}

Если переданный в «db.User.get» колбэк выполнится асинхронно — вне контекста блока «try – catch» — ошибки внутри этой возвратной функции не будут обработаны. Поэтому для обработки исключений в возвратных функциях в Node.js пользуйтесь проверенной схемой - первым аргументом возвратной функции ожидайте ошибку:

1
2
3
callmeAsync(function (err, arg1, arg2, ...) {
// ... handle error
});

ОШИБКА #7 Математические операции с числами

Все числа в Javascript – с плавающей десятичной запятой, целочисленного типа данных нет. При этом числа с плавающей запятой могут содержать целочисленные представления только до определённого значения, при превышении которого и начинаются ошибки в вычислениях. Например, следующее выражение (как ни парадоксально) в Node.js вернёт значение true:

1
Math.pow(2, 53)+1 === Math.pow(2, 53)

К сожалению, это ещё не все сюрпризы при работе с числами. При том, что сами числа — с плавающей запятой, операторы, используемые в подсчётах, предзназначены для работы с целыми типами данных. Поэтому получаем:

1
2
5 % 2 === 1 // true
5 >> 1 === 2 // true

В отличие от арифметических операторов, поразрядные операторы и операторы сдвига работают только с 32 битами таких чисел. Поэтому сдвиг Math.pow(2, 53) на 1 всегда вернёт значение 0. Это же число в поразрядной операции ИЛИ вернёт значене 1:

1
2
3
Math.pow(2, 53) / 2 === Math.pow(2, 52) // true
Math.pow(2, 53) >> 1 === 0 // true
Math.pow(2, 53) | 1 === 1 // true

Это всё не кажется серьёзной проблемой, поскольку работать с большими числами приходится редко. Но если возникает такая необходимость, есть немало библиотек для точных математических операций с большими числами, например, node-bigint.

ОШИБКА #8 Неиспользование потоковых API

Допустим, мы хотим создать небольшой прокси-сервер, который обрабатывает запросы, принимая данные с другого веб-сервера. Например, веб сервер, обрабатывающий Gravatar изображения.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var http = require('http')
var crypto = require('crypto')
http.createServer()
.on('request', function(req, res) {
var email = req.url.substr(req.url.lastIndexOf('/')+1)
if(!email) {
res.writeHead(404)
return res.end()
}
var buf = new Buffer(1024*1024);
http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {
var size = 0
resp.on('data', function(chunk) {
chunk.copy(buf, size)
size += chunk.length
})
.on('end', function() {
res.write(buf.slice(0, size))
res.end()
})
})
})
.listen(8080);
}

В данном примере сервер получает изображение с Gravatar, помещает его в буфер, затем обрабатывает запрос. Такой вариант допустим, поскольку аватарки обычно небольшого размера. Но представьте, что данные, которые мы проксируем, весят тесячи мегабайт. Тогда лучше поступить так:

1
2
3
4
5
6
7
8
9
10
11
12
13
http.createServer()
.on('request', function(req, res) {
var email = req.url.substr(req.url.lastIndexOf('/')+1)
if(!email) {
res.writeHead(404)
return res.end()
}
http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {
resp.pipe(res)
})
})
.listen(8080)
}

Как видно из примера, мы получаем изображение с Gravatar и сразу передаём в поток клиенту. Этап буферизации данных полностью исключён.

ОШИБКА #9 Использование console.log() для отладки

console.log() – удобный способ для вывода в консоль любых ваших действий. Передайте объект — в консоли получите объектный литерал. console.log() принимает любое количество аргументов и выводит их, отделив пробелами. Одним словом, искушение пользоваться таким способом для отладки багов велико. Но всё же не рекомендую. Некоторые в процессе отладки используют console.log() крайне активно, и так и оставляют его в коде в закомментированных строках. Лучше в таких ситуациях воспользуйтесь какой-нибудь специально предназначенной для этих целей библиотекой, например debug.

С помощью этой библиотеки можно автоматически отключить режим отладки при запуске приложения. Например, можно избежать вывода любых сообщений об ошибках в терминал, если не устанавливать переменную среды отладки DEBUG:

1
2
3
// app.js
var debug = require('debug')('app’')
debug('Hello, %s!','world')

Соответственно, чтобы ошибки выводились, установите переменную со значением «app» или «*»:

1
DEBUG=app node app.js

ОШИБКА #10 Неиспользование диспетчера

Как на стадии девелопмента, так и в продакшене, очень полезно использовать программу-диспетчер для управления всеми программными процессами. Опытные разработчики часто говорят, что в случае сбоя приложения, не нужно пытаться обработать ошибку — позвольте приложению прекратить работу, и диспетчер перезапустит его в считанные секунды. И это ещё не всё, на что способен диспетчер: помимо перазапуска при сбое, он также может перезапускать приложение после обновления файлов. Одним словом, разрабатывать на Node.js с диспетчером куда приятнее, чем без него.

Примеры программ-диспетчеров:

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

Вместо заключения

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

Читайте так же: 10 типичных ошибок Node.js разработчиков, Часть 1

По мотивам Mahmud Ridwan