Как работать с node js на windows

Мы начинаем публикацию серии материалов, которые представляют собой поэтапный перевод руководства по Node.js для начинающих. А именно, в данном случае «начинающи...

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

Кстати, в прошлом году у нас был похожий по масштабам проект, посвящённый bash-скриптам. Тогда мы, после публикации всех запланированных материалов, собрали их в виде PDF-файла. Так же планируется поступить и в этот раз.

Сегодня мы обсудим особенности Node.js, начнём знакомство с экосистемой этой платформы и напишем серверный «Hello World».

[Советуем почитать] Другие части цикла

Часть 1: Общие сведения и начало работы
Часть 2: JavaScript, V8, некоторые приёмы разработки
Часть 3: Хостинг, REPL, работа с консолью, модули
Часть 4: npm, файлы package.json и package-lock.json
Часть 5: npm и npx
Часть 6: цикл событий, стек вызовов, таймеры
Часть 7: асинхронное программирование
Часть 8: Руководство по Node.js, часть 8: протоколы HTTP и WebSocket
Часть 9: Руководство по Node.js, часть 9: работа с файловой системой
Часть 10: Руководство по Node.js, часть 10: стандартные модули, потоки, базы данных, NODE_ENV
Полная PDF-версия руководства по Node.js

Обзор Node.js

Node.js — это опенсорсная кроссплатформенная среда выполнения для JavaScript, которая работает на серверах. С момента выпуска этой платформы в 2009 году она стала чрезвычайно популярной и в наши дни играет весьма важную роль в области веб-разработки. Если считать показателем популярности число звёзд, которые собрал некий проект на GitHub, то Node.js, у которого более 50000 звёзд, это очень и очень популярный проект.

Платформа Node.js построена на базе JavaScript движка V8 от Google, который используется в браузере Google Chrome. Данная платформа, в основном, используется для создания веб-серверов, однако сфера её применения этим не ограничивается.

Рассмотрим основные особенности Node.js.

▍Скорость

Одной из основных привлекательных особенностей Node.js является скорость. JavaScript-код, выполняемый в среде Node.js, может быть в два раза быстрее, чем код, написанный на компилируемых языках, вроде C или Java, и на порядки быстрее интерпретируемых языков наподобие Python или Ruby. Причиной подобного является неблокирующая архитектура платформы, а конкретные результаты зависят от используемых тестов производительности, но, в целом, Node.js — это очень быстрая платформа.

▍Простота

Платформа Node.js проста в освоении и использовании. На самом деле, она прямо-таки очень проста, особенно это заметно в сравнении с некоторыми другими серверными платформами.

▍JavaScript

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

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

▍Движок V8

В основе Node.js, помимо других решений, лежит опенсорсный JavaScript-движок V8 от Google, применяемый в браузере Google Chrome и в других браузерах. Это означает, что Node.js пользуется наработками тысяч инженеров, которые сделали среду выполнения JavaScript Chrome невероятно быстрой и продолжают работать в направлении совершенствования V8.

▍Асинхронность

В традиционных языках программирования (C, Java, Python, PHP) все инструкции, по умолчанию, являются блокирующими, если только разработчик явным образом не позаботится об асинхронном выполнении кода. В результате если, например, в такой среде, произвести сетевой запрос для загрузки некоего JSON-кода, выполнение потока, из которого сделан запрос, будет приостановлено до тех пор, пока не завершится получение и обработка ответа.

JavaScript значительно упрощает написание асинхронного и неблокирующего кода с использованием единственного потока, функций обратного вызова (коллбэков) и подхода к разработке, основанной на событиях. Каждый раз, когда нам нужно выполнить тяжёлую операцию, мы передаём соответствующему механизму коллбэк, который будет вызван сразу после завершения этой операции. В результате, для того чтобы программа продолжила работу, ждать результатов выполнения подобных операций не нужно.

Подобный механизм возник в браузерах. Мы не можем позволить себе ждать, скажем, окончания выполнения AJAX-запроса, не имея при этом возможности реагировать на действия пользователя, например, на щелчки по кнопкам. Для того чтобы пользователям было удобно работать с веб-страницами, всё, и загрузка данных из сети, и обработка нажатия на кнопки, должно происходить одновременно, в режиме реального времени.

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

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

Node.js предоставляет разработчику неблокирующие базовые механизмы ввода вывода, и, в целом, библиотеки, использующиеся в среде Node.js, написаны с использованием неблокирующих парадигм. Это делает блокирующее поведение кода скорее исключением, чем нормой.

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

▍Библиотеки

Благодаря простоте и удобству работы с менеджером пакетов для Node.js, который называется npm, экосистема Node.js прямо-таки процветает. Сейчас в реестре npm имеется более полумиллиона опенсорсных пакетов, которые может свободно использовать любой Node.js-разработчик.
Рассмотрев некоторые основные особенности платформы Node.js, опробуем её в действии. Начнём с установки.

Установка Node.js

Node.js можно устанавливать различными способами, которые мы сейчас рассмотрим.
Так, официальные установочные пакеты для всех основных платформ можно найти здесь.

Существует ещё один весьма удобный способ установки Node.js, который заключается в использовании менеджера пакетов, имеющегося в операционной системе. Например, менеджер пакетов macOS, который является фактическим стандартом в этой области, называется Homebrew. Если он в вашей системе есть, вы можете установить Node.js, выполнив эту команду в командной строке:

brew install node

Список менеджеров пакетов для других операционных систем, в том числе — для Linux и Windows, можно найти здесь.

Популярным менеджером версий Node.js является nvm. Это средство позволяет удобно переключаться между различными версиями Node.js, с его помощью можно, например, установить и попробовать новую версию Node.js, после чего, при необходимости, вернуться на старую. Nvm пригодится и в ситуации, когда нужно испытать какой-нибудь код на старой версии Node.js.

Я посоветовал бы начинающим пользоваться официальными установщиками Node.js. Пользователям macOS я порекомендовал бы устанавливать Node.js с помощью Homebrew. Теперь, после того, как вы установили Node.js, пришло время написать «Hello World».

Первое Node.js-приложение

Самым распространённым примером первого приложения для Node.js можно назвать простой веб-сервер. Вот его код:

const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hello Worldn')
})
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`)
})

Для того чтобы запустить этот код, сохраните его в файле server.js и выполните в терминале такую команду:

node server.js

Для проверки сервера откройте какой-нибудь браузер и введите в адресной строке http://127.0.0.1:3000, то есть — тот адрес сервера, который будет выведен в консоли после его успешного запуска. Если всё работает как надо — на странице будет выведено «Hello World».

Разберём этот пример.

Для начала, обратите внимание на то, что код содержит команду подключения модуля http.

Платформа Node.js является обладателем замечательного стандартного набора модулей, в который входят отлично проработанные механизмы для работы с сетью.

Метод createServer() объекта http создаёт новый HTTP-сервер и возвращает его.

Сервер настроен на прослушивание определённого порта на заданном хосте. Когда сервер будет готов, вызывается соответствующий коллбэк, сообщающий нам о том, что сервер работает.

Когда сервер получает запрос, вызывается событие request, предоставляющее два объекта. Первый — это запрос (req, объект http.IncomingMessage), второй — ответ (res, объект http.ServerResponse). Они представляют собой важнейшие механизмы обработки HTTP-запросов.

Первый предоставляет в наше распоряжение сведения о запросе. В нашем простом примере этими данными мы не пользуемся, но, при необходимости, с помощью объекта req можно получить доступ к заголовкам запроса и к переданным в нём данным.

Второй нужен для формирования и отправки ответа на запрос.

В данном случае ответ на запрос мы формируем следующим образом. Сначала устанавливаем свойство statusCode в значение 200, что указывает на успешное выполнение операции:

res.statusCode = 200

Далее, мы устанавливаем заголовок Content-Type:

res.setHeader('Content-Type', 'text/plain')

После этого мы завершаем подготовку ответа, добавляя его содержимое в качестве аргумента метода end():

res.end('Hello Worldn')

Мы уже говорили о том, что вокруг платформы Node.js сформировалась мощная экосистема. Обсудим теперь некоторые популярные фреймворки и вспомогательные инструменты для Node.js.

Фреймворки и вспомогательные инструменты для Node.js

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

  • Express. Эта библиотека предоставляет разработчику предельно простой, но мощный инструмент для создания веб-серверов. Ключом к успеху Express стал минималистический подход и ориентация на базовые серверные механизмы без попытки навязать некое видение «единственно правильной» серверной архитектуры.
  • Meteor. Это — мощный фулстек-фреймворк, реализующий изоморфный подход к разработке приложений на JavaScript и к использованию кода и на клиенте, и на сервере. Когда-то Meteor представлял собой самостоятельный инструмент, включающий в себя всё, что только может понадобиться разработчику. Теперь он, кроме того, интегрирован с фронтенд-библиотеками, такими, как React, Vue и Angular. Meteor, помимо разработки обычных веб-приложений, можно использовать и в мобильной разработке.
  • Koa. Этот веб-фреймворк создан той же командой, которая занимается работой над Express. При его разработке, в основу которой легли годы опыта работы над Express, внимание уделялось простоте решения и его компактности. Этот проект появился как решение задачи внесения в Express серьёзных изменений, несовместимых с другими механизмами фреймворка, которые могли бы расколоть сообщество.
  • Next.js. Этот фреймворк предназначен для организации серверного рендеринга React-приложений.
  • Micro. Это — весьма компактная библиотека для создания асинхронных HTTP-микросервисов.
  • Socket.io. Это библиотека для разработки сетевых приложений реального времени.

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

Краткая история Node.js

В этом году Node.js исполнилось уже 9 лет. Это, конечно, не так уж и много, если сравнить этот возраст с возрастом JavaScript, которому уже 23 года, или с 25-летним возрастом веба, существующем в таком виде, в котором мы его знаем, если считать от появления браузера Mosaic.

9 лет — это маленький срок для технологии, но сейчас возникает такое ощущение, что платформа Node.js существовала всегда.

Я начал работу с Node.js с ранних версий платформы, когда ей было ещё только 2 года. Даже тогда, несмотря на то, что информации о Node.js было не так уж и много, уже можно было почувствовать, что Node.js — это очень серьёзно.

Теперь поговорим о технологиях, лежащих в основе Node.js и кратко рассмотрим основные события, связанные с этой платформой.

Итак, JavaScript — это язык программирования, который был создан в Netscape как скриптовый язык, предназначенный для управления веб-страницами в браузере Netscape Navigator.

Частью бизнеса Netscape была продажа веб-серверов, которые включали в себя среду, называемую Netscape LiveWire. Она позволяла создавать динамические веб-страницы, используя серверный JavaScript. Как видите, идея использования JS для серверной разработки гораздо старше чем Node.js. Этой идее почти столько же лет, сколько и самому JavaScript, но во времена, о которых идёт речь, популярности серверный JS не снискал.

Одним из ключевых факторов, благодаря которому платформа Node.js стала столь распространённой и популярной, является время её появления. Так, за несколько лет до этого JavaScript начали считать серьёзным языком. Случилось это благодаря приложениям Web 2.0, вроде Google Maps или Gmail, которые продемонстрировали миру возможности современных веб-технологий.

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

Конечно же, популярность Node.js основана не только на удачном стечении обстоятельств и на том, что эта платформа появилась в правильное время. Она представила миру инновационный подход к серверной разработке на JavaScript. Рассмотрим основные вехи истории Node.js.

▍2009

  • Появление Node.js
  • Создание первого варианта npm.

▍2010

  • Появление Express.
  • Появление Socket.io.

▍2011

  • Выход npm 1.0.
  • Большие компании, такие, как LinkedIn и Uber, начали пользоваться Node.js.

▍2012

  • Быстрый рост популярности Node.js.

▍2013

  • Появление Ghost, первой крупной платформы для публикаций, использующей Node.js.
  • Выпуск Koa.

▍2014

  • В этом году произошли драматические события. Появился проект IO.js, являющийся форком Node.js, целью создания которого, кроме прочего, было внедрение поддержки ES6 и ускорение развития платформы.

▍2015

  • Основание организации Node.js Foundation.
  • Слияние IO.js и Node.js.
  • В npm появляется возможность работать с приватными модулями.
  • Выход Node.js 4 (надо отметить, что версий 1, 2 и 3 у этой платформы не было).

▍2016

  • Инцидент с пакетом left-pad.
  • Появление Yarn.
  • Выход Node.js 6.

▍2017

  • В npm начинают больше внимания уделять безопасности.
  • Выход Node.js 8
  • Появление поддержки HTTP/2.
  • V8 официально признают в качестве JS-движка, предназначенного не только для Chrome, но и для Node.
  • Еженедельно осуществляется 3 миллиарда загрузок из npm.

▍2018

  • Выход Node.js 10.
  • Поддержка ES-модулей.
  • Экспериментальная поддержка mjs.

Итоги

Сегодня вы ознакомились с платформой Node.js, разобрались с её установкой, написали и испытали первое простое приложение. В следующий раз мы поговорим о том, каким объёмом знаний в области JavaScript нужно обладать для успешной разработки для Node.js, о том, чем различаются браузерный и серверный JS-код, и обсудим некоторые приёмы Node.js-разработки.

Уважаемые читатели! Скажите, запустился ли у вас Hello World для Node.js?

Следующие части руководства:
Часть 1: Общие сведения и начало работы
Часть 2: JavaScript, V8, некоторые приёмы разработки
Часть 3: Хостинг, REPL, работа с консолью, модули
Часть 4: npm, файлы package.json и package-lock.json
Часть 5: npm и npx
Часть 6: цикл событий, стек вызовов, таймеры
Часть 7: асинхронное программирование

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Продолжать ли перевод цикла статей про Node.js?


85.84%
Категорически да :)
564


11.11%
С удовольствием добавлю в закладки
73

Проголосовали 657 пользователей.

Воздержались 49 пользователей.

Автором было замечено множество комментариев в духе “я скачал/скачала Node.js, и что теперь?”. Этот урок отвечает на данный вопрос и объясняет новичку, с чего начать.

Очень много путаницы у новичков из-за непонимания того, что такое Node.js. И даже описание с официального сайта не помощник.

Важную вещь, которую нужно осознать — Node не является веб-сервером. Сама по себе платформа ничего не делает. Она не работает как Apache. Нет конфигурационных файлов, в которых она указывает вам на HTML-файлы. Если вы хотите, чтобы платформа была HTTP-сервером, вам придётся написать HTTP-сервер (с помощью встроенных библиотек).

Node.js — это просто другой способ выполнять код на вашем компьютере. Это среда выполнения языка JavaScript.

Устанавливаем Node

Node.js легко установить. Достаточно зайти на страницу загрузки официального сайта.

Я установил, и что теперь?

После установки у вас появилась новая команда в командной строке “node”. Вы можете использовать Node двумя способами.

  • Первый — без аргументов: данная команда откроет интерактивный режим в командной строке, где вы можете исполнять код JavaScript.$ node > console.log(‘Hello World’); Hello World undefined

В этом примере я просто набрал “console.log(‘Hello World’);” и нажал на Enter. Node начнет выполнять, и мы увидим наше сообщение. Также он напишет “undefined”, потому что печатает возвращаемое значение, иconsole.log не возвращает ничего.   — Другой способ использования Node.js — это создание файла Javascript.

Итак, создаем файл:

hello.js

console.log('Hello World');

И сохраняем его в директорию, из которой будем запускать этот файл. Чтобы перейти достаточно в командной строке написать cd полное_название_директории (ну или можно использовать относительную адресацию, о которой можно почитать здесь.

Запускаем в командной строке:

$ node hello.js
Hello World

В данном случае мы переместили сообщение файла console.log и отправили этот файл команде node как аргумент. Node запускает код JavaScript в файле и распечатывает “Hello World”.

Файлы ввода/вывода с node.js

Запуск чистого JavaScript — это здорово, но не очень полезно. Поэтому в Node.js есть огромное количество библиотек (модулей) для того, чтобы делать реальные вещи. В данном примере мы откроем файл с записями и будем его обрабатывать.

example_log.txt

2013-08-09T13:50:33.166Z A 2
2013-08-09T13:51:33.166Z B 1
2013-08-09T13:52:33.166Z C 6
2013-08-09T13:53:33.166Z B 8
2013-08-09T13:54:33.166Z B 5

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

Нам нужно считать содержание файла.

my_parser.js

// Загружаем модуль файловой системы
var fs = require('fs');

// Считываем содержание файла в память
fs.readFile('example_log.txt', function (err, logData) {
 
	// Если возникла ошибка, мы кидаем исключение
	// и программа заканчивается
	if (err) throw err;
 
	// logData это объект типа Buffer, переводим в строку
  		var text = logData.toString();
});

К счастью, Node.js сильно облегчает обработку содержания файла с помощью встроенного модуля filesystem (fs). Модуль fs имеет функцию readFile, которая берёт путь к файлу и обратный вызов. Обратный вызов будет исполнен, когда файл будет полностью прочитан. Данные файла попадают в форме типа Buffer, что является набором битов. Мы можем конвертировать в строку с помощью функции toString()

Теперь добавим парсировщик (он написан на чистом JavaScript).

my_parser.js

// загружаем модуль filesystem(fs)
var fs = require('fs');
 
// считываем содержание файла в память
fs.readFile('example_log.txt', function (err, logData) {
 
    // Если возникла ошибка, мы кидаем исключение
	// и программа заканчивается
    if (err) throw err;
 
    // logData имеет тип Buffer, конвертируем в строку
    var text = logData.toString();
 
var results = {};
 
// Разбиваем текст на массив из строчек
var lines = text.split('n');
 
lines.forEach(function(line) {
    var parts = line.split(' ');
    var letter = parts[1];
    var count = parseInt(parts[2]);
 
if(!results[letter]) {
      results[letter] = 0;
}
 
results[letter] += parseInt(count);
});
 
console.log(results);
// { A: 2, B: 14, C: 6 }
});

Когда файл будет аргументом команды node, результат распечатается, и будет осуществлён выход.

$ node my_parser.js
{ A: 2, B: 14, C: 6 }

Асинхронные вызовы в node.js

Как вы заметили в прошлом примере, для Node.js характерно использование асинхронных вызовов. По существу вы пишете, что нужно делать, и когда это будет сделано, будет вызван обратный вызов, потому что node.js однопоточен. Пока вы ждёте запуска обратного вызова, Node.js может уйти и делать другие вещи вместо блокировки до завершения запроса.

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

Создание веб-сервера с помощью node.js

Как было уже сказано, Node ничего не может делать сам по себе. Один из встроенных модулей позволяет легко создать простой HTTP сервер, пример его использования приведен на главной странице официального сайта.

mywebserver.js

var http = require('http');
 
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello Worldn');
}).listen(8080);
 
console.log('Server running on port 8080.');

Ещё раз: это базовый веб-сервер, а не сервер с полным функционалом. Он не может обслуживать картинки или HTML-файлы. На самом деле, какой бы вы ни отправили запрос, он возвратит “Hello World”. Однако, вы можете запустить этот скрипт, перейти по адресу http://localhost:8080 в своем браузере и увидеть этот текст.

Вы уже могли заметить, что ваше приложение на node.js больше не останавливается. Всё потому, что вы создали веб-сервер, и ваше приложение на node.js отвечает на запросы до тех пор, пока вы сами его не остановите.

Если вы хотите сделать веб-сервер с полной функциональностью, вам придётся проделать некоторый объем работы: нужно будет проверить все запросы, считать подходящие файлы и отправить вывод обратно. Есть и хорошие новости. Люди уже проделали эту тяжелую работу за вас.

Модуль Express для node.js

Express — это фреймворк, который облегчит создание большинства обычных сайтов. Сперва вам будет необходимо установить его. Вместе с командой node у вас будет доступ к команде “npm”. Этот инструмент даёт доступ к огромному количеству модулей, созданных сообществом, в том числе и Express.

$ cd /my/app/location
$ npm install express

Когда вы установите модуль, он будет положен в папку node_modules в директории вашего приложения. Теперь вы сможете использовать его функциональность так, как будто он встроен.

Давайте создадим базовый статический сервер с использованием Express.

mystaticfile_server.js

var express = require('express'),
    app = express();
 
app.use(express.static(__dirname + '/public'));
 
app.listen(8080);

Запускаем скрипт:

$ node mystaticfile_server.js

Теперь у вас есть довольно умелый статический файловый сервер. Всё, что вы положили в папку public, может быть запрошено и показано браузером: HTML, картинки, почти всё, что угодно. Так, например, если вы положите картинку под названием “my_image.png” внутрь папки public, вы можете открыть картинку прямо в браузере, перейдя по адресу http://localhost:8080/my_image.png. Конечно, Express имеет довольно обширную функциональность, и вы можете изучить её в ходе разработки.

NPM

Мы чуть-чуть затронули NPM в предыдущей секции, но мы хотели бы подчеркнуть, как важен этот инструмент при обычной разработке в Node.js. Тысячи модулей доступны для решения почти всех типичных проблем, с которыми вы, вероятно, можете столкнуться. Помните, что стоит проверить NPM прежде, чем изобретать велосипед. Для типичного приложения на node.js характерно иметь множество подключенных модулей.

В предыдущем примере мы вручную установили Express. Если у вас много подключенных библиотек, устанавливать их все вряд ли будет хорошей затеей. Поэтому npm использует файл package.json.

package.json

{
  "name" : "MyStaticServer",
  "version" : "0.0.1",
  "dependencies" : {
    "express" : "3.3.x"
  }
}

Файл package.json содержит обзор вашего приложения. Здесь много доступных полей, но представлен необходимый минимум. Секция “dependency” описывает название и версию модулей, которые вы бы хотели установить. В данном случае мы принимаем любую версию Express.3.3. В данной секции вы можете перечислить столько библиотек, сколько вам угодно.

Теперь вместо того, чтобы устанавливать каждую библиотеку отдельно, мы можем запустить одну команду и установить всё необходимое одним махом.

Когда вы запустите команду, npm будет искать в текущей папке файл package.json. Если найдет, то установит каждую библиотеку из списка.

Организация кода в node.js

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

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

parser.js

// Конструктор обработчика
var Parser = function() {
 
};
 
// Обрабатывает заданный текст
Parser.prototype.parse = function(text) {
 
var results = {};
 
// Текст превращает в массив строчек
var lines = text.split('n');
 
lines.forEach(function(line) {
    var parts = line.split(' ');
    var letter = parts[1];
    var count = parseInt(parts[2]);
 
if(!results[letter]) {
    results[letter] = 0;
}
 
results[letter] += parseInt(count);
});
 
return results;
};
 
// Экспортирует конструктор типа Parser из этого модуля
module.exports = Parser;

То, что мы сделали, было созданием нового файла, содержащего логику для обработки записей. Это только стандартный код JavaScript, и существует множество способов внедрить этот код. Мы выбрали то, как определить новый объект JavaScript, и его легко модульно протестировать.

Важной строчкой является “module.exports”. Это пояснение для Node, что вы экспортируете из этого файла. В данном случае я экспортирую конструктор, поэтому пользователи могут создавать экземпляры моего объекта типа Parser. Вы можете экспортировать, что захотите.

// Require my new parser.js file.
var Parser = require('./parser');
 
// Load the fs (filesystem) module.
var fs = require('fs');
 
// Read the contents of the file into memory.
fs.readFile('example_log.txt', function (err, logData) {
 
// If an error occurred, throwing it will
  // display the exception and kill our app.
  if (err) throw err;
 
// logData is a Buffer, convert to string.
  var text = logData.toString();
 
// Create an instance of the Parser object.
  var parser = new Parser();
 
// Call the parse function.
  console.log(parser.parse(text));
  // { A: 2, B: 14, C: 6 }
});

Файлы включены точно так же, как и модули, с одной разницей: вы предоставляете путь к файлу вместо имени. Расширение .js подразумевает, что вы можете оставить привычку писать расширение в конце, если хотите.

Так как мы экспортировали конструктор, то это то, что будет возвращено из запроса. Теперь мы можем создавать экземпляры объектов типа Parser и использовать их.

Вывод

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

Мы хотим напомнить, что использование Node.js ограничено только вашей фантазией. Библиотеки ядра очень аккуратно разработаны и предоставляют любые инструменты для построения приложения. Скомбинировав все модули, доступные в npm, вы можете удивиться, насколько быстро можно строить крайне трудные и настолько же интересные приложения.

Другие статьи по теме

Подборка бесплатных ресурсов для изучения Node.js

Подборка материалов по JavaScript

Источник: Absolute Beginners Guide to Node.js

Что такое Node JS. Начало работы

Последнее обновление: 13.05.2021

Node.js представляет среду выполнения кода на JavaScript, которая построена на основе движка JavaScript Chrome V8, который позволяет
транслировать вызовы на языке JavaScript в машинный код. Node.js прежде всего предназначен для создания серверных приложений на языке JavaScript. Хотя
также существуют проекты по написанию десктопных приложений (Electron) и даже по созданию кода для микроконтроллеров. Но прежде всего мы говорим о Node.js,
как о платформе для создания веб-приложений.

Node.js является открытым проектом, исходники которого можно посмотреть на github.com.

Установка

Для загрузки перейдет на официальный сайт https://nodejs.org/en/. На главной странице мы сразу увидим две возможные опции для загрузки:
самая последняя версия NodeJS и LTS-версия.

Загрузка NodeJS

Загрузим последнюю версию. В моем случае это версия 16.1.0. Для Windows установщик представляет файл с расширением msi. После запуска откроется программа установщика:

Установка Node JS на Windows

После успешной установки вы можем ввести в командной строке/терминале команду node -v, и нам отобразится текущая версия node.js:

C:WINDOWSsystem32>node -v
v16.1.0

Версии node.js для других операционных систем наряду с исходниками можно найти по адресу https://nodejs.org/en/download/

Инструменты разработки

Для разработки под Node JS достаточно простейшего текстового редактора, в частности, Notepad++. Также можно использовать более изощренные редакторы типа Atom, Sublime,
Visual Studio Code, либо среды разработки, которые поддерживают работу с Node.JS, например, Visual Studio или WebStorm.

REPL

После установки NodeJS нам становится доступным такой инструмент как REPL. REPL (Read Eval Print Loop) представляет возможность запуска выражений на языке JavaScript в командной строке или терминале.

Так, запустим командную строку (на Windows) или терминал (на OS X или Linux) и введем команду node. После ввода этой команды
мы можем выполнять различные выражения на JavaScript:

C:WINDOWSsystem32>node
Welcome to Node.js v16.1.0
Type ".help" for more information.
> 2+6
8
>

Или используем какую-нибудь функцию JS:

> console.log("Hello NodeJS");
Hello NodeJS
undefined
>

Можно определять свои функции и затем их вызывать, например, возведение числа в квадрат:

> function square(x){return x * x;}
undefined
>square(5)
25
>

Если мы введем что-то неправильно, то REPL укажет об ошибке:

REPL in Node JS

Выполнение файла

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

console.log("Hello world");

В командной строке перейдем с помощью команды cd к каталогу helloapp, а затем выполним команду:

Данная команда выполнит код из файла app.js:

Выполнение файла в Node.js

О проекте

Цель данного документа — помочь вам начать разработку приложений на Node.js и научить всему, что необходимо знать о «продвинутом» JavaScript.
Это больше, чем обычный «Hello world»-туториал.

Статус

Вы читаете финальную версию этой книги, в обновлениях исправляются только ошибки или отражаются изменения в новых версиях Node.js. Последнее обновление 12 Февраля 2012.

Код примеров этой книги тестировался на Node.js версии 0.8.8 (сверено с англ. версией —прим.перев.).

Целевая аудитория

Вероятно, документ будет полезен читателям с базовыми знаниями, примерно, как у меня: опыт работы хотя бы с одним объектно-ориентированным языком, таким как Ruby, Python, PHP или Java, небольшой опыт в Javascript и полный новичок в Node.js.

Документ рассчитан на разработчиков, уже знакомых с другими языками программирования.
Это значит, что здесь не приводится объяснение действительно базовых вещей, таких как типы данных, переменные, управляющие структуры и т. д.
Вы должные разбираться в этих понятиях, чтобы понимать эту книгу.

Однако, поскольку функции и объекты в JavaScript отличаются от своих аналогов в других языках, они будут описаны достаточно подробно.

Структура учебника

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

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

Мы начнём с выяснения того, чем JavaScript в Node.js отличается от JavaScript в браузере.

Далее, мы остановимся на написании традиционного «Hello world»-приложения, которое является наиболее простым примером «что-то делающего» кода Node.js.

Тогда мы обсудим, какое «реальное» приложение мы хотим создать, проанализируем компоненты, которые необходимо реализовать для написания данного приложения, и начнём работать над каждым из них, шаг за шагом.

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

Исходный код законченного приложения доступен в
the NodeBeginnerBook Github репозитории.

Содержание

  • О проекте
    • Статус
    • Целевая аудитория
    • Структура учебника
  • JavaScript и Node.js
    • JavaScript и Вы
    • Предупреждение
    • Server-side JavaScript
    • «Hello World»
  • Полномасштабное веб-приложение с Node.js
    • Что должно делать наше приложение
    • Задачи
  • Реализация приложения
    • Простой HTTP-сервер
    • Анализ нашего HTTP-сервера
    • Передача функций в качестве параметра
    • Как анонимная функция делает наш HTTP-сервер рабочим
    • Событийно-ориентированные обратные вызовы
    • Как наш сервер обрабатывает запросы
    • Выбор места для нашего серверного модуля
    • Что необходимо для «роутера»?
    • Исполнение королевских постановлений в царстве глаголов
    • Роутинг реальных обработчиков запроса
    • Создание ответа обработчиков запроса
      • Как делать не надо
      • Блокирование и неблокирование
      • Ответ обработчиков запроса с неблокирующими операциями.
    • Сделаем что-нибудь полезное
      • Обработка POST-запросов
      • Обработка загрузки файлов
    • Выводы и перспективы

JavaScript и Node.js

JavaScript и Вы

До того как мы поговорим о технических вещах, позвольте занять некоторое время и поговорить о вас и ваших отношениях с JavaScript.
Эта глава позволит вам понять, имеет ли смысл читать дальше.

Скорее всего, как и в моем случае, вы начали свой путь в веб-разработке с написания простых статических HTML-документов.
Вместе с этим, вы познакомились с веселой штукой, называемой JavaScript, но использовали его исключительно в простых случаях, добавляя интерактивности на ваши веб-странички.

Что вы хотели узнать — так это действительно полезные вещи; вы хотели знать, как создать сложный сайт.
Для этого вы изучали PHP, Ruby, Java и начинали писать backend-код.

Тем не менее, вы постоянно следили за JavaScript, вы видели, что с появлениям JQuery, Prototype и других фреймворков этот язык стал больше, чем просто window.open().

Однако, это всё ещё относилось к frontend-разработке.
Конечно, jQuery — очень мощный инструмент, но всякий раз, когда вы приправляли ваш сайт разными jQuery-«фишками», в лучшем случае, вы были JavaScript-пользователем нежели JavaScript-разработчиком.

А потом пришел Node.js. JavaScript на сервере: насколько это хорошо?

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

В этом — как раз и проблема.
JavaScript живёт двумя, может даже тремя разными жизнями: весёлый маленький DHMTL-помощник из середины 90-х годов, более серьезный frontend-инструмент в лице jQuery и наконец серверный (server-side, backend) JavaScript.
По этой причине не так просто найти информацию, которая поможет вам познать правильный JavaScript, пригодный для написания Node.js приложения в манере, дающий ощущение, что вы не просто использовали JavaScript, а действительно разрабатывали на JavaScript.

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

Конечно, существует отличная документация по Node.js, но её зачастую недостаточно. Нужно руководство.

Моя цель заключается в обеспечении вас руководством.

Предупреждение

Существуют действительно отличные специалисты в области JavaScript. Я не из их числа.

Я — действительно, тот парень, о котором написано в предыдущем параграфе.
Я знаю кое-что о разработке backend веб-приложений, но я всё ещё новичок в «реальном» JavaScript и всё ещё новичок в Node.js.
Я узнал некоторые продвинутые аспекты JavaScript совсем недавно.
Я неопытен.

Вот почему эта книга не из разряда «от новичка к эксперту», а скорее «от новичка к продвинутому новичку».

Если всё удастся, то этот документ станет тем руководством, которое я хотел бы иметь, когда начинал в Node.js.

Server-side JavaScript

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

Node.js — действительно, просто другой контекст: он позволяет вам запускать JavaScript-код вне браузера.

Чтобы ваш JavaScript код выполнился на вычислительной машине вне браузера (на backend), он должен быть интерпретирован и, конечно же, выполнен.
Именно это и делает Node.js. Для этого он использует движок V8 VM от Google — ту же самую среду исполнения для JavaScript, которую использует браузер Google Chrome.

Кроме того, Node.js поставляется со множеством полезных модулей, так что вам не придется писать всё с нуля, как, например, вывод строки в консоль.

Таким образом, Node.js состоит из 2 вещей: среды исполнения и полезных библиотек.

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

«Hello world»

Хорошо, давайте пойдём сразу с места в карьер и напишем наше первое Node.js-приложение: «Hello world».

Откройте ваш любимый редактор и создайте файл под названием helloworld.js.
Мы хотим вывести строку «Hello world» в консоль, для этого пишем следующий код:

console.log("Hello World");

Сохраняем файл и выполняем его посредством Node.js:

node helloworld.js

Это должно вывести Hello World на наш терминал.

Ладно, всё это скучно, правда? Давайте напишем что-нибудь полезное.

Полномасштабное веб-приложение с Node.js

Что должно делать наше приложение

Возьмём что-нибудь попроще, но приближенное к реальности:

  • Пользователь должен иметь возможность использовать наше приложение с браузером;
  • Пользователь должен видеть страницу приветствия по адресу http://domain/start;
  • Когда запрашивается http://domain/upload, пользователь должен иметь возможность загрузить картинку со своего компьютера и просмотреть её в своем браузере.

Вполне достаточно. Конечно, вы могли бы достичь этой цели, немного погуглив и поговнокодив. Но это не то, что нам нужно.

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

Задачи

Давайте проанализируем наше приложение.
Что нужно, чтобы его реализовать:

  • У нас — онлайн веб-приложение, поэтому нам нужен HTTP-сервер;
  • Нашему серверу необходимо обслуживать различные запросы в зависимости от URL, по которому был сделан запрос. Для этого нам нужен какой-нибудь роутер (маршрутизатор), чтобы иметь возможность направлять запросы определенным обработчикам;
  • Для выполнения запросов, пришедших на сервер и направляемые роутером, нам нужны действующие обработчики запросов;
  • Роутер, вероятно, должен иметь дело с разными входящими POST-данными и передавать их обработчикам запросов в удобной форме. Для этого нам нужен какой-нибудь обработчик входных данных;
  • Мы хотим не только обрабатывать запросы, но и показывать пользователю контент по запрошенным URL-адресам, поэтому нам нужна некая логика отображения для обработчиков запросов, чтобы иметь возможность отправлять контент пользовательскому браузеру;
  • Последнее, но не менее важное — пользователь сможет загружать картинки, поэтому нам нужен какой-нибудь обработчик загрузки, который возьмёт на себя заботу о деталях.

Давайте подумаем о том, как бы мы реализовали это на PHP.
Скорее всего, типичное решение будет на HTTP-сервере Apache с установленным mod_php5.

Это относится к первому пункту наших задач, то есть, «принимать HTTP-запросы и отправлять готовые веб-странички пользователю» — вещи, которые PHP сам не делает.

С Node.js — немного иначе.
Потому что в Node.js мы не только создаем наше приложение, мы также реализуем полноценный HTTP-сервер.
Действительно, наше веб-приложение и веб-сервер — в сущности, одно и тоже.

Может показаться, что это приведет к лишней работе, но сейчас вы увидите, что с Node.js это не так.

Давайте просто начнём реализовывать нашу первую задачу — HTTP-сервер.

Реализация приложения

Простой HTTP-сервер

Когда я подошел к моменту создания своего первого «реального» Node.js-приложения, я задался вопросом, как организовать мой код.

Я должен делать всё в одном файле?
Большинство учебных пособий в интернете учат как создавать простой HTTP-сервер в Node.js, сохраняя всю логику в одном месте.
Что, если я хочу быть уверенным, что мой код останется читабельным по мере реализации всё большего функционала.

На самом деле, достаточно легко отыскивать проблемные участки вашего кода, который разделён на модули.

Это позволяет вам иметь чистый главный файл, который вы исполняете в Node.js и чистые модули, которые могут использоваться главным файлом и друг другом.

Так, давайте создадим главный файл, который мы будем использовать для запуска нашего приложения, и файл модуля, в котором будет находиться наш HTTP-сервер.

Я думаю, это более-менее традиционно назвать главным файлом index.js.
А код нашего сервера имеет смысл поместить в файл под названием server.js.

Давайте начнём с модуля сервера.
Создайте файл server.js в корневой директории вашего проекта и поместите туда следующий код:

var http = require("http");

http

.createServer(function(request, response) {
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello World");
  response
.end();
}).listen(8888);

И всё!
Вы написали работающий HTTP-сервер.
Давайте проверим его, запустив и протестировав.
Во-первых, выполните ваш скрипт в Node.js:

node server.js

Теперь откройте ваш браузер и перейдите по адресу http://localhost:8888/.
Должна вывестись веб-страница со строкой «Hello world».

Правда, это довольно интересно?
Как насчёт того, чтобы поговорить о том, что здесь происходит и оставить на потом вопрос о том, как организовать наш проект?
Я обещаю, мы вернемся к нему.

Анализ нашего HTTP-сервера

Хорошо, тогда давайте проанализируем, что здесь действительно происходит.

Первая строчка подключает http-модуль, который поставляется вместе с Node.js и делает его доступным через переменную http.

Далее, мы вызываем одну из функций http-модуля createServer.
Эта функция возвращает объект, имеющий метод listen, принимающий числовое значение порта нашего HTTP-сервера, который необходимо прослушивать.

Пожалуйста, проигнорируйте функцию, которая определяется внутри скобок http.createServer.

Мы могли бы написать код, который запускает наш сервер, прослушивающий порт 8888, так:

var http = require("http");var server = http.createServer();
server
.listen(8888);

Это запустило бы HTTP-сервер прослушивающего порт 8888, который больше ничего не делает (даже не отвечает на входящие запросы).

Действительно интересная (и, если вы привыкли к более консервативным языкам как PHP, довольно странная) часть — это определение функции там, где вы бы ожидали увидеть первый параметр для createServer().

Оказывается, эта определяемая функции и есть первый (и только) параметр, который мы передаём в createServer() при вызове.
Потому что в JavaScript функции могут быть переданы как параметр в другую функцию.

Передача функций в качестве параметра

Вы можете в качестве примера сделать что-то подобное:

function say(word) {
  console
.log(word);
}function execute(someFunction, value) {
  someFunction
(value);
}

execute

(say, "Hello");

Разберите пример внимательно! Здесь мы передаём функцию say как первый параметр функции execute.
Не значение, которое возвращает функция say, а саму функцию say!

Таким образом, say становится локальной переменной someFunction внутри execute и execute может вызвать функцию в этой переменной вот так: someFunction() (то есть, добавив скобки).

Конечно же, так как say принимает один параметр (word), execute может передать какое-либо значение в качестве этого параметра, когда вызывает someFunction.

Мы можем, что мы и сделали, передать функцию как параметр в другую функцию.
Но мы не обязаны применять этот косвенный способ, когда сначала определяется функция, а потом передаётся как параметр.
Мы можем определить и передать функцию как параметр в другую функцию прямо на месте:

function execute(someFunction, value) {
  someFunction
(value);
}

execute

(function(word){ console.log(word) }, "Hello");

Мы определяем функцию, которую хотим передать в execute, прямо там, где у execute должен быть первый параметр.

Из-за того, что нам даже не надо давать имя этой функции, её называют анонимная функция.

Это первый проблеск, который я называю «продвинутый» JavaScript, но давайте всё по порядку.
А сейчас давайте просто примем то, что в JavaScript мы можем передать функцию как параметр, когда вызываем другую функцию.
Мы можем сделать это путём присвоения нашей функции переменной, которую му передаем, или путём определения функции для передачи на месте.

Как анонимная функция делает наш HTTP-сервер рабочим

С этими знаниями давайте вернемся назад к нашему минималистичному HTTP-серверу:

var http = require("http");

http

.createServer(function(request, response) {
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello World");
  response
.end();
}).listen(8888);

Сейчас должно быть ясно, что мы здесь делаем: передаём в функцию createServer анонимную функцию.

Мы можем добиться того же самого через рефакторинг нашего кода:

var http = require("http");function onRequest(request, response) {
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello World");
  response
.end();
}

http

.createServer(onRequest).listen(8888);

Может сейчас самое время спросить: Почему мы это делаем так?

Событийно-ориентированные обратные вызовы

Ответ на вопрос a) не так легко дать (по крайней мере для меня), и b) кроется в самой природе работы Node.js — это событийно-ориентированность, то, благодаря чему он работает так быстро.

Возможно, вы захотите занять немного своего времени и почитать отличный пост Felix Geisendörfer Понимание node.js, чтобы прояснить этот момент.

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

Когда вызываем метод http.createServer, мы, конечно, не только хотим иметь сервер, слушающий какой-то порт.
Мы также хотим что-нибудь сделать, когда приходит HTTP-запрос на этот сервер.

Проблема состоит в асинхронности: запрос происходит в любой момент времени, в то время как у нас только один процесс, в котором запущен наш сервер.

Когда пишем PHP-приложения, мы не беспокоимся обо всем этом: всякий раз, когда приходит HTTP-запрос, веб-сервер (обычно Apache) ответвляет новый процесс специально для этого запроса и запускает соответствующий PHP-скрипт с нуля, который выполняется от начала до конца.

Когда приходит новый запрос на порт 8888, относительно потоков управления, мы находимся в середине нашей Node.js-программы.
Как это понять, чтоб не помешаться?

Это как раз то, где событийно-ориентированный дизайн Node.js/JavaScript на самом деле помогает. Нам надо узнать некоторые новые понятия, чтобы досконально понять всё это.

Мы создаем сервер и передаём функцию в созданный им метод.
Всякий раз, когда наш сервер получает запрос, переданная нами функция будет вызываться.

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

Этот принцип называется обратный вызов или callback.
Мы передаём в некоторый метод функцию и этот метод исполняет её, когда происходит связанное с методом событие.

По крайней мере для меня, это заняло некоторое время, чтобы понять.
Просто почитайте блог Felix Geisendörfer снова, если вы всё ещё не уверены.

Давайте немного поиграем с этим новым понятием.
Можем ли мы доказать, что наш код продолжает работать после создания сервера, даже если нет HTTP-запроса и callback-функция, переданная нами, не вызывается?
Давайте попробуем:

var http = require("http");function onRequest(request, response) {
  console
.log("Request received.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello World");
  response
.end();
}

http

.createServer(onRequest).listen(8888);

console

.log("Server has started.");

Обратите внимание, что я использую console.log для вывода текста «Request received.», когда срабатывает функция onRequest (наш callback), а текст «Server has started.» — сразу после запуска HTTP-сервера.

Когда мы запустим этот код (как обычно, node server.js), он тут же выведет в командной строке «Server has started.».
Всякий раз, когда мы делаем запрос нашему серверу (через переход по адресу http://localhost:8888/ в нашем браузере), в командной строке выводится сообщение «Request received.».

Объектно-ориентированный асинхронный серверный JavaScript с callback-ми в действии :-)

(Обратите внимание, что наш сервер, возможно, будет выводить «Request received.» в консоль 2 раза при открытии страницы в браузере.
Это происходит из-за того, что большинство браузеров будут пытаться загрузить фавикон по адресу http://localhost:8888/favicon.ico при запросе http://localhost:8888/)

Как наш сервер обрабатывает запросы

Хорошо, давайте быстро проанализируем остальной код сервера внутри тела нашей callback-функции onRequest().

Когда callback запускается и наша функция onRequest() срабатывает, в неё передаются 2 параметра: request и response.

Они являются объектами и вы можете использовать их методы для обработки пришедшего HTTP-запроса и ответа на запрос (то есть, просто что-то посылать по проводам обратно в браузер, который запрашивал ваш сервер).

И наш код делает именно это: Всякий раз, когда запрос получен, он использует функцию response.writeHead() для отправки HTTP-статуса 200 и Content-Type в заголовке HTTP-ответа, а функцию Response.Write() для отправки текста «Hello World» в теле HTTP-ответа.

И последнее, мы вызываем response.end() чтобы завершить наш ответ.

На данный момент, мы не заботимся о деталях запроса, поэтому мы не используем объект request полностью.

Выбор места для нашего серверного модуля

Я обещал, что мы вернёмся к организации нашего приложения.
У нас есть код очень простого HTTP-сервера в файле server.js и я упоминал, что общепринято иметь главный файл с названием index.js, который используется для начальной загрузки и запуска нашего приложения, путём использования других модулей приложения (таких как наш модуль HTTP-сервера в server.js).

Давайте поговорим о том, как сделать server.js настоящим Node.js-модулем, чтобы его можно было использовать в нашем главном файле index.js.

Как вы могли заметить, мы уже использовали модули в нашем коде:

var http = require("http");...

http

.createServer(...);

Где-то внутри Node.js живёт модуль под названием «http» и мы можем использовать его в нашем коде, путём подключения и присвоения его результата локальной переменной.

Это делает нашу локальную переменную объектом, содержащим в себе все публичные методы модуля http.

Общепринитая практика — использовать имя модуля для имени локальной переменной, но мы свободны в своём выборе делать, как нам нравится:

var foo = require("http");...

foo

.createServer(...);

Теперь понятно, как использовать внутренние модули Node.js.
А как создать свой собственный модуль и как его использовать?

Давайте выясним это, превратив наш скрипт server.js в настоящий модуль.

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

Сейчас функционал нашего HTTP-сервера надо экспортировать, что довольно просто: скрипты, подключающие наш модуль сервера, просто запускают сервер.

Чтобы сделать это возможным, поместим код нашего сервера в функцию под название start и будем экспортировать эту функцию:

var http = require("http");function start() {
 
function onRequest(request, response) {
    console
.log("Request received.");
    response
.writeHead(200, {"Content-Type": "text/plain"});
    response
.write("Hello World");
    response
.end();
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Теперь мы можем создать наш основной файл index.js, и запускать наш HTTP-сервер там, хотя код для сервера находится всё ещё в файле server.js.

Создаём файл index.js со следующим содержимым:

var server = require("./server");

server

.start();

Как вы могли видеть, мы можем использовать модуль сервера просто как внешний модуль: вызвав этот файл и определив для него переменную, экспортированные функции становятся доступны нам.

Вот и всё. Сейчас мы можем запустить наше приложение через главный скрипт и он будет делать всё то же самое, что и раньше:

node index.js

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

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

В очень простом приложении мы могли бы делать это напрямую внутри callback-функции onRequest().
Но, как я говорил, давайте добавим немного больше абстракции, чтобы сделать наш пример интереснее.

Задание соответствия между разными HTTP-запросами и разными частями нашего кода называется «маршрутизация» («routing», роутинг).
Давайте тогда создадим модуль под названием router.

Что необходимо для «роутера»?

Нам нужно иметь возможность скармливать запрошенный URL и возможные добавочные GET- и POST-параметры нашему роутеру и, с учётом этого, роутер должен определять, какой код выполнять (этот код есть третья составляющая нашего приложения: коллекция обработчиков запросов, делающие необходимую работу по определённому запросу).

Итак, нам надо рассматривать HTTP-запрос и извлекать запрошенный URL, а также GET/POST-параметры.
Можно поспорить, должен ли этот код быть частью роутера или сервера (или даже своего собственного модуля), но давайте сейчас пока просто сделаем его частью сервера.

Вся необходимая нам информация доступна через объект request, который передается в качестве первого параметра нашей callback-функции onRequest().
Чтобы интерпретировать эту информацию, нам необходимо добавить кое-какие Node.js-модули, а именно url и querystring.

Модуль url поддерживает методы, которые позволяют нам извлекать различные части URL (такие как запрошенный путь (URL path) и строка параметров запроса (query string)), а querystring в свою очередь, используется для парсинга строки параметров запроса (query string):

                               url.parse(string).query
                                       |
       url.parse(string).pathname      |
                   |                   |
                   |                   |
                 ------ -------------------
http://localhost:8888/start?foo=bar&hello=world
                            ---       -----
                             |          |
                             |          |
          querystring(string)["foo"]    |
                                        |
                     querystring(string)["hello"]

Конечно, мы также можем использовать querystring для парсинга тела POST-запроса, как мы увидим далее.

Давайте сейчас добавим в нашу функцию onRequest() логику, необходимую для извлечения пути URL (pathname), запрошенного браузером:

var http = require("http");
var url = require("url");function start() {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");
    response
.writeHead(200, {"Content-Type": "text/plain"});
    response
.write("Hello World");
    response
.end();
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Замечательно.
Теперь наше приложение может различать запросы на основе запрошенного пути URL.
Это позволяет нам направлять запросы нашим обработчикам запросов в зависимости от пути URL, используя наш роутер.
Таким образом, мы можем строить наше приложение RESTful-путём, потому что теперь можем реализовать интерфейс, следующий принципам Идентификации ресурсов (смотри статью в википедии REST для справки).

В контексте нашего приложения, это означает, что мы сможем обрабатывать запросы с URL /start и /upload разными частями нашего кода.
Скоро мы увидим, как всё соединяется вместе.

Теперь самое время написать наш роутер.
Создаём новый файл под названием router.js со следующим содержимым:

function route(pathname) {
  console
.log("About to route a request for " + pathname);
}

exports

.route = route;

Конечно этот код ничего не делает, но сейчас этого достаточно.
Давайте сначала посмотрим, как скрепить этот роутер с нашим сервером до того как поместим больше логики в роутер.

Нашему HTTP-серверу необходимо знать о роутере и использовать его.
Мы могли бы жёстко прописать эти зависимости в нашем сервере, но, так как мы знаем только сложные способы из нашего опыта в других языках программирования, мы сделаем слабосвязанную зависимость сервера и роутера через внедрение этих зависимостей (можете почитать отличный пост Мартина Фаулера по внедрениям зависимости на английском языке или статью в Википедии на русском языке для дополнительной информации).

Для начала, расширим нашу серверную функцию start(), чтобы дать нам возможность передавать функцию route() как параметр:

var http = require("http");
var url = require("url");function start(route) {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");

    route

(pathname);

    response

.writeHead(200, {"Content-Type": "text/plain"});
    response
.write("Hello World");
    response
.end();
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Теперь расширим наш index.js соответственно, то есть внедрим функцию route() нашего роутера в сервер:

var server = require("./server");
var router = require("./router");

server

.start(router.route);

Мы опять передаём функцию, которая не является чем-то новым для нас.

Если мы сейчас запустим наше приложение (node index.js, как обычно) и запросим какой-нибудь URL, вы сможете увидеть в консоли, что наш HTTP-сервер использует наш роутер и передает ему запрошенный pathname:

bash$ node index.js
Request for /foo received.
About to route a request for /foo

(Я опустил слегка надоедливый вывод для запроса /favicon.ico)

Исполнение королевских постановлений в царстве глаголов

Позвольте мне ещё раз побродить вокруг и около и снова поговорить о функциональном программировании.

Передача функций связана не только с техническими соображениями.
Относительно разработки программного обеспечения это — почти философия.
Просто подумайте: в нашем index-файле мы могли бы передавать объект router в наш сервер и сервер мог бы вызывать функцию route этого объекта.

Этим способом мы бы передавали нечто и сервер использовал бы это нечто, чтобы сделать что-то.
Эй, роутер, не могли бы вы показать мне маршрут?

Но серверу не нужно нечто.
Ему нужно только получить что-то сделанное, а чтоб получить уже что-то сделанное, вам не нужно нечто совсем, вам необходимо действие.
Вам не нужно существительное, вам нужен глагол.

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

Я понял это, когда читал шедевр Стива Йегге Execution in the Kingdom of Nouns (частичный перевод на русский Исполнение королевских постановлений в царстве существительных).
Почитайте это обязательно. Это одно из лучших произведений о программировании, которое я когда-либо имел удовольствие встречать.

Роутинг реальных обработчиков запроса

Вернёмся к делу.
Наш HTTP-сервер и наш роутер запросов сейчас — лучшие друзья, и общаются друг с другом так, как мы хотели.

Конечно, этого недостаточно.
«Роутинг» подразумевает, что мы хотим обрабатывать запросы на разные URL по-разному.
Мы хотели бы иметь «бизнес-логику» для запросов к /start в одной функции, а для запросов к /upload в другой.

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

Давайте эти функции, в которые направляются запросы, назовём обработчиками запросов.
И давайте возьмёмся за них сейчас, потому что делать что-либо с роутером сейчас пока не имеет смысла.

Новая часть приложения, новый модуль — здесь никаких сюрпризов.
Создадим модуль под названием requestHandlers, добавим болванки функций для каждого обработчика запроса и экспортируем их как методы модуля:

function start() {
  console
.log("Request handler 'start' was called.");
}function upload() {
  console
.log("Request handler 'upload' was called.");
}

exports

.start = start;
exports
.upload = upload;

Это позволяет нам связать обработчики запросов с роутером, давая нашему роутеру что-нибудь маршрутизировать.

В этот момент мы должны принять решение: захардкодить использование модуля requestHandlers в роутере или мы хотим ещё немного внедрения зависимостей?
Хотя внедрение зависимостей, как и любой другой паттерн, не должен использоваться только ради того, чтобы быть использованным, в нашем случае имеет смысл сделать слабосвязанную пару роутера и обработчиков запроса и, таким образом, сделать роутер действительно многоразовым.

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

Как мы собираемся передать их?
Сейчас у нас есть два обработчика, но в реальном приложении это число будет увеличиваться и меняться.
И мы уверены, что не хотим возиться с роутером каждый раз, когда добавляется новый URL + обработчик запроса.
И какие-нибудь if запрос == x then вызвать обработчик y в роутере будут более чем убоги.

Переменное число элементов и каждому соответствует строка (запрашиваемый URL)?
Так, похоже на ассоциативный массив, это наиболее подходящее.

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

Об этом есть хорошее введение http://msdn.microsoft.com/en-us/magazine/cc163419.aspx.
Позвольте мне процитировать подходящую часть:

В C++ или C#, когда мы говорим об объектах, мы ссылаемся на экземпляры классов или структуры.
Объекты имеют разные свойства и методы, в зависимости от шаблонов (классов), экземплярами которых они являются.
Но не в случае с JavaScript-объектами.
В JavaScript, объекты — это просто коллекция пар имя/значение — JavaScript-объект — это как словарь со строковыми ключами.

Если JavaScript-объекты это просто коллекции пар имя/значение, как тогда у них могут быть методы?
Итак, значения могут быть строками, числами и т.д. или функциями!

Хорошо, наконец-то возвращаемся к нашему коду. Мы решили, что мы хотим передать список из requestHandlers как объект и, для того, чтобы достичь слабое связывание, мы хотим внедрить этот объект в route().

Начнём с добавления объекта в наш главный файл index.js:

var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");var handle = {}
handle
["/"] = requestHandlers.start;
handle
["/start"] = requestHandlers.start;
handle
["/upload"] = requestHandlers.upload;

server

.start(router.route, handle);

Хотя handle — это больше из разряда «нечто» (коллекция обработчиков запроса), я, всё-таки, предлагаю называть его глаголом, потому что в результате это будет функциональное выражение в нашем роутере, как вы скоро увидите.

Как вы можете видеть, это действительно просто — назначать различные URL соответствующему обработчику запроса: просто добавляя пару ключ/значение из «/» и requestHandlers.start, мы можем выразить красивым и аккуратным способом, что не только запросы к «/start», но также и запросы к «/» должны быть обработаны обработчиком start.

После определения объекта мы передали его в сервер как дополнительный параметр.
Изменим наш server.js, чтобы использовать его:

var http = require("http");
var url = require("url");function start(route, handle) {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");

    route

(handle, pathname);

    response

.writeHead(200, {"Content-Type": "text/plain"});
    response
.write("Hello World");
    response
.end();
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Мы добавили параметр handle в функцию start() и передаём объект handle в callback-функцию route() в качестве перового параметра.

Соответственно, изменим функцию route() в нашем файле router.js:

function route(handle, pathname) {
  console
.log("About to route a request for " + pathname);
 
if (typeof handle[pathname] === 'function') {
    handle
[pathname]();
 
} else {
    console
.log("No request handler found for " + pathname);
 
}
}

exports

.route = route;

Что мы здесь делаем — мы проверяем, существует ли обработчик запроса для данного пути, и если существует, просто вызываем соответствующую функцию.
Из-за того, что мы имеем доступ к нашим функциям обработчиков запроса из нашего объекта просто, как если бы имели доступ к элементу ассоциативного массива, у нас есть это прекрасное выражение handle[pathname]();, о котором говорилось ранее: «Пожалуйста, handle этот pathname».

Хорошо, это всё, что нужно, чтобы связать сервер, роутер и обработчики запроса вместе!
При запуске нашего приложения и запроса http://localhost:8888/start в браузере, мы можем убедиться, что надлежащий обработчик запроса действительно был вызван:

Server has started.
Request for /start received.
About to route a request for /start
Request handler 'start' was called.

Так же открываем http://localhost:8888/ в нашем браузере и убеждаемся, что эти запросы в самом деле обрабатываются обработчиком запросов start:

Request for / received.
About to route a request for /
Request handler 'start' was called.

Создание ответа обработчиков запроса

Замечательно. Вот только если бы обработчики запроса могли отправлять что-нибудь назад браузеру, было бы ещё лучше, правильно?

Вспомните, «Hello World», который выводит ваш браузер в запрошенной странице, всё ещё исходит от функции onRequest в нашем файле server.js.

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

Как делать не надо

Прямой подход, который мы захотим использовать как разработчики с опытом в PHP или Ruby, на самом деле ложный: он может прекрасно работать, иметь большой смысл, а потом, когда мы этого не ждём, неожиданно всё развалится.

Под «прямым подходом» я подразумеваю использование в обработчиках запроса return «» для контента, который надо показать пользователю, и отправлять этот ответ в функцию onRequest назад пользователю.

Давайте просто сделаем это и тогда увидим, почему это не такая уж и хорошая идея.

Мы начнём с обработчиков запроса и заставим их возвращать то, что хотели бы показать в браузере.
Нам надо изменить requestHandlers.js вот так:

function start() {
  console
.log("Request handler 'start' was called.");
 
return "Hello Start";
}function upload() {
  console
.log("Request handler 'upload' was called.");
 
return "Hello Upload";
}

exports

.start = start;
exports
.upload = upload;

Хорошо. Также, роутер должен вернуть серверу то, что обработчики запроса вернули ему.
Поэтому надо отредактировать router.js так:

function route(handle, pathname) {
  console
.log("About to route a request for " + pathname);
 
if (typeof handle[pathname] === 'function') {
   
return handle[pathname]();
 
} else {
    console
.log("No request handler found for " + pathname);
   
return "404 Not found";
 
}
}

exports

.route = route;

Как видим, возвращается некоторый текст «404 Not found», если запрос не может быть маршрутизирован.

И самое последнее, но не менее важное, нам нужен рефакторинг нашего сервера, чтобы заставить его отвечать браузеру с контентом обработчиков запроса, возвращаемых через роутер. Трансформируем server.js в:

var http = require("http");
var url = require("url");function start(route, handle) {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");

    response

.writeHead(200, {"Content-Type": "text/plain"});
   
var content = route(handle, pathname)
    response
.write(content);
    response
.end();
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Если запустим наше написаное приложение, всё будет работать замечательно: запрос http://localhost:8888/start выдаст в браузере результат «Hello Start», запрос http://localhost:8888/upload даст нам «Hello Upload», а http://localhost:8888/foo выведет «404 Not found».

OK, тогда в чём проблема?
Короткий ответ: потому что мы столкнемся с проблемами, если один из обработчиков запроса захочет использовать неблокирующую операцию в будущем.

Подробный ответ займёт немного больше времени.

Блокирование и неблокирование

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

Вместо того, чтобы объяснять, что такое «блокирование» и «неблокирование», давайте продемонстрируем себе, что произойдёт, если мы добавим блокирующую операцию в наши обработчики запроса.

Для этого модифицируем обработчик запроса start так, чтобы он ждал 10 секунд до того как вернёт свою строку «Hello Start».
В JavaScript нет такой штуки как sleep(), поэтому мы будем использовать хитрый хак.

Пожалуйста, измените requestHandlers.js как описано далее:

function start() {
  console
.log("Request handler 'start' was called.");function sleep(milliSeconds) {
   
var startTime = new Date().getTime();
   
while (new Date().getTime() < startTime + milliSeconds);
 
}

  sleep

(10000);
 
return "Hello Start";
}function upload() {
  console
.log("Request handler 'upload' was called.");
 
return "Hello Upload";
}

exports

.start = start;
exports
.upload = upload;

Просто объясню, что этот код делает: когда функция start() вызвана, Node.js ожидает 10 секунд и только тогда возвращает «Hello Start».
Когда вызывается upload(), она выполняется немедленно, как и раньше.

(Конечно, вы уже поняли, вместо засыпания на 10 секунд, в start() могут быть реальные блокирующие операции, такие как сложные длительные вычисления.)

Давайте посмотрим, что поменялось.

Как обычно, нам надо перезапустить сервер.
На этот раз я попрошу вас следовать немного более сложному «протоколу», чтобы увидеть, что произошло: во-первых, откройте браузер или таб.
В первом окне браузера, введите, пожалуйста, http://localhost:8888/start в адресную строку, но не переходите пока по этому адресу!

В адресную строку второго окна браузера введите http://localhost:8888/upload и снова не переходите по адресу.

Теперь сделайте, как описано далее: нажмите клавишу Enter в первом окне («/start»), а затем быстро переключитесь на второе окно («/upload») и нажмите тоже Enter.

Что вы будете наблюдать: URL /start потребуется 10 секунд для загрузки, как мы и ожидали.
Но URL /upload так же потребуется 10 секунд на загрузку, хотя в соответствующем обработчике запроса нет sleep()!

Почему? Потому что start() содержит блокирующую операцию.
Like in «it’s blocking everything else from working».

И в этом проблема, потому что, как говорят: «В node всё работает параллельно, за исключением вашего кода».

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

Но для этого нам надо использовать обратные вызовы, передавая функции внутри тех функций, которые могут сделать то, что занимает некоторое время (как, например, sleep() на 10 секунд или запрос к базе данных или какое-то дорогостоящее вычисление.)

Таким образом, мы как бы говорим: «Эй, возможноДолгаяФункция(), пожалуйста, сделай вот это, но я, однопотоковый Node.js, не собираюсь ждать здесь, пока ты закончишь, я продолжу выполнение строчек кода ниже тебя, а ты возьми пока вот эту функцию callbackFunction() и вызови её, когда всё сделаешь. Спасибо!»

(Если хотите почитать об этом более подробно, пожалуйста посмотрите пост Mixu на Understanding the node.js event loop.)

И мы сейчас увидим, почему способ, которым мы создали «обработчик запроса обрабатывающий ответ» в нашем приложении не позволит правильно использовать неблокирующие операции.

Ещё раз давайте попробуем испытать проблему на своей шкуре, модифицировав наше приложение.

Мы снова используем наш обработчик запроса start. Пожалуйста, измените его следующим образом (файл requestHandlers.js)

var exec = require("child_process").exec;function start() {
  console
.log("Request handler 'start' was called.");
 
var content = "empty";

  exec

("ls -lah", function (error, stdout, stderr) {
    content
= stdout;
 
});return content;
}function upload() {
  console
.log("Request handler 'upload' was called.");
 
return "Hello Upload";
}

exports

.start = start;
exports
.upload = upload;

Как можно видеть, мы просто внедрили новый модуль Node.js child_process.
Мы сделали так, потому что это позволит нам использовать очень простую, но полезную неблокирующую операцию: exec().

Что делает exec() — она выполняет shell-команду внутри Node.js.
В этом примере мы собираемся использовать её, чтобы получить список всех файлов в текущей директории («ls -lah»), позволяя нам отобразить этот список в браузере пользователя, запросившего URL /start.

Что делает этот код: создает новую переменную content (с начальным значением «empty»), выполняет «ls -lah», заполняет переменную результатом и возвращает её.

Как обычно, запустим наше приложение и посетим http://localhost:8888/start.

Которая загрузит нам красивую страничку со строкой «empty». Что тут не так?

Ну, как вы уже догадались, exec() делает свою магию в неблокирующий манере.
Это хорошая штука, потому что таким образом мы можем выполнять очень дорогостоящие shell-операции (как, например, копирование больших файлов или что-то подобное), не заставляя наше приложение полностью останавливаться, пока блокирующая sleep-операция не выполнится.

Если хотите удостовериться, замените «ls -lah» на более дорогостоящую операцию «find /»).

Но мы не совсем довольны своей элегантной неблокирующей операцией, когда наш браузер не отображает её результат, не так ли?

Давайте тогда пофиксим это. Давайте попытаемся понять, почему текущая архитектура не работает.

Проблемой является то, что exec(), чтобы работать без блокирования, использует callback-функцию.

В нашем примере это анонимная функция, которая передаётся как второй параметр в функцию exec():

function (error, stdout, stderr) {
  content
= stdout;
}

И здесь лежит корень нашей проблемы: наш собственный код исполняется синхронно, что означает, что сразу после вызова exec(), Node.js продолжит выполнять return content;.
К этому моменту content ещё «empty», из-за того, что callback-функция, переданная в exec(), до сих пор не вызвана — потому что операция exec() асинхронная.

Теперь «ls -lah» — очень недорогая и быстрая операция (если только в директории не миллион файлов).
Именно поэтому callback вызывается относительно оперативно — но это, всё же, происходит асинхронно.

Использование более дорогостоящих команд делает это более очевидным: «find /» занимает около 1 минуты на моей машине, но если я заменяю «ls -lah» на «find /» в обработчике запроса, то я всё ещё немедленно получаю HTTP-ответ, когда открываю URL /start.
Ясно, что exec() делает что-то в фоновом режиме, пока Node.js продолжает исполнять приложение и мы можем предположить, что callback-функция, которую мы передали в exec(), будет вызвана только когда команда «find /» закончит выполняться.

Но как нам достичь нашей цели, то есть, показать пользователю список файлов в текущей директории?

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

Ответ обработчиков запроса с неблокирующими операциями.

Я употребил фразу «правильный способ».
Опасная вещь.
Довольно часто не существует единого «правильного способа».

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

Сейчас наше приложение способно транспортировать контент (который обработчики запроса хотели бы показать пользователю) от обработчиков запроса к HTTP-серверу, возвращая его через слои приложения (обработчик запроса -> роутер -> сервер).

Наш новый подход заключается в следующем: вместо доставки контента серверу мы будем сервер доставлять к контенту.
Чтобы быть более точным, мы будем внедрять объект response (из серверной callback-функции onRequest()) через роутер в обработчики запроса.
Обработчики смогут тогда использовать функции этого объекта для ответа на сами запросы.

Достаточно разъяснений. Вот — пошаговый рецепт изменения нашего приложения.

Начнём с нашего server.js:

var http = require("http");
var url = require("url");function start(route, handle) {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");

    route

(handle, pathname, response);
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Вместо ожидания возврата значения от функции route(), мы передаём наш объект response в качестве третьего параметра.
Кроме того, мы удалили всякие вызовы методов response из обработчика onRequest(), потому что мы рассчитываем, что route позаботится об этом.

Далее идёт router.js:

function route(handle, pathname, response) {
  console
.log("About to route a request for " + pathname);
 
if (typeof handle[pathname] === 'function') {
    handle
[pathname](response);
 
} else {
    console
.log("No request handler found for " + pathname);
    response
.writeHead(404, {"Content-Type": "text/plain"});
    response
.write("404 Not found");
    response
.end();
 
}
}

exports

.route = route;

Та же схема: вместо ожидания возврата значения от наших обработчиков события, мы передаём объект respond.

Если обработчик запроса не может быть использован, мы заботимся об ответе с надлежащим заголовком «404» и телом ответа.

И последнее, но не менее важное, мы модифицируем requestHandlers.js:

var exec = require("child_process").exec;function start(response) {
  console
.log("Request handler 'start' was called.");

  exec

("ls -lah", function (error, stdout, stderr) {
    response
.writeHead(200, {"Content-Type": "text/plain"});
    response
.write(stdout);
    response
.end();
 
});
}function upload(response) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello Upload");
  response
.end();
}

exports

.start = start;
exports
.upload = upload;

Наши функции-обработчики должны принять параметр response и использовать его, чтобы ответить на запрос напрямую.

Обработчик start будет отвечать изнутри анонимного обратного вызова exec(), а обработчик upload будет всё ещё выдавать «Hello Upload», но теперь посредством объекта response.

Если вы запустили наше приложение снова (node index.js), всё должно работать как и ожидалось.

Если хотите убедиться, что дорогостоящая операция в /start больше не будет блокировать запросы на /upload, модифицируйте ваш requestHandlers.js как показано далее:

var exec = require("child_process").exec;function start(response) {
  console
.log("Request handler 'start' was called.");

  exec

("find /",
   
{ timeout: 10000, maxBuffer: 20000*1024 },
   
function (error, stdout, stderr) {
      response
.writeHead(200, {"Content-Type": "text/plain"});
      response
.write(stdout);
      response
.end();
   
});
}function upload(response) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello Upload");
  response
.end();
}

exports

.start = start;
exports
.upload = upload;

Благодаря этому, HTTP-запросы к http://localhost:8888/start будут занимать не менее 10 секунд, но запросы к http://localhost:8888/upload будут получать ответ немедленно, даже если /start всё ещё занят вычислениями.

Сделаем что-нибудь полезное

До сих пор мы делали всё прекрасно и изысканно, но мы не создали ничего значимого для клиентов нашего супер-сайта.

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

OK, давайте шаг за шагом, но с разъяснением больших техник и принципов JavaScript, и в то же время, давайте немного ускоримся.
Автору слишком нравится слушать самого себя.

Здесь «шаг за шагом» означает примерно 2 шага: сначала мы посмотрим как обрабатывать входящие POST-запросы (но не загрузку файла), и на втором шаге мы используем внешний модуль Node.js для обработки загрузки файла.
Я выбирал этот подход по двум причинам.

Во-первых, обрабатывать базовые POST-запросы относительно просто в Node.js, но для обучения это — достаточно стоящее упражнение.

Во-вторых, обработка загрузки файла (к примеру, multipart POST-запросы) это не так просто в Node.js, поэтому выходит за рамки этого учебника, но пример использования для этого внешнего модуля имеет смысл включить в учебник для начинающих.

Обработка POST-запросов

Давайте сделаем попроще: предоставим текcтовое поле, которое может быть заполнено пользователем и отправлено на сервер в POST-запросе.
После получения и обработки этого запроса мы отобразим содержимое текстового поля.

HTML-код для формы текстового поля должен формировать наш обработчик запроса /start, так давайте сразу же добавим его в файл requestHandlers.js:

function start(response) {
  console
.log("Request handler 'start' was called.");var body = '<html>'+
   
'<head>'+
   
'<meta http-equiv="Content-Type" content="text/html; '+
   
'charset=UTF-8" />'+
   
'</head>'+
   
'<body>'+
   
'<form action="/upload" method="post">'+
   
'<textarea name="text" rows="20" cols="60"></textarea>'+
   
'<input type="submit" value="Submit text" />'+
   
'</form>'+
   
'</body>'+
   
'</html>';

    response

.writeHead(200, {"Content-Type": "text/html"});
    response
.write(body);
    response
.end();
}function upload(response) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("Hello Upload");
  response
.end();
}

exports

.start = start;
exports
.upload = upload;

Если теперь этот код не выиграет Webby Awards, то я не знаю, какой сможет.
Вы должны увидеть эту очень простую форму, когда запросите http://localhost:8888/start в вашем браузере.
Если это не так — возможно, вы не перезагрузили приложение.

Я уже слышу вас: помещать содержимое представления прямо в обработчик запроса некрасиво.
Тем не менее, я решил не включать этот дополнительный уровень абстракции (то есть, разделение представления и логики) в наш учебник, потому что, я думаю, что это не научит нас чему-нибудь стоящему в контексте JavaScript или Node.js.

Давайте лучше использовать появившееся окно для более интересных проблем, то есть, обработки POST-запроса в нашем обработчике запроса /upload при отправке этой формы пользователем.

Теперь, когда мы стали экспертными новичками, мы уже не удивляемся тому факту, что обработка POST-данных делается в неблокирующей манере, через использование асинхронных callback-ов.

Это имеет смысл, потому что POST-запросы могут быть потенциально очень большими — ничто не мешает пользователю ввести текст размером в несколько мегабайтов.
Обработка всего массива данных за один раз может привести к блокирующей операции.

Чтобы сделать весь процесс неблокирующим, Node.js обслуживает POST-данные небольшими порциями, а callback-функции вызываются при определённых событиях.
Эти события — data (когда приходит новая порция POST-данных) и end (когда все части данных были получены).

Надо сообщить Node.js, какие функции вызывать, когда эти события произойдут.
Это делается путём добавления слушателей (listeners) в объект request, который передаётся в нашу callback-функцию onRequest, когда HTTP-запрос получен.

В основном, это выглядит так:

request.addListener("data", function(chunk) {
 
// called when a new chunk of data was received
});

request

.addListener("end", function() {
 
// called when all chunks of data have been received
});

Возникает вопрос, где реализовать эту логику.
В настоящее время мы можем получить доступ к объекту request только в нашем сервере — мы не передаём его в роутер и в обработчики запроса, как делаем это с объектом response.

На мой взгляд, это работа HTTP-сервера — предоставлять приложению все данные из запросов.
Поэтому я предлагаю обрабатывать POST-данные прямо в сервере и передавать конечные данные в роутер и обработчики запроса, которые сами решат, что с ними делать.

Таким образом, идея — в том, чтобы поместить обратные вызовы событий data и end в сервер, собирать все куски POST-данных в data и вызывать роутер при получении события end, пока идёт передача собранных порций данных в роутер, который в свою очередь передаёт их в обработчики запроса.

Начинаем с server.js:

var http = require("http");
var url = require("url");function start(route, handle) {
 
function onRequest(request, response) {
   
var postData = "";
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");

    request

.setEncoding("utf8");

    request

.addListener("data", function(postDataChunk) {
      postData
+= postDataChunk;
      console
.log("Received POST data chunk '"+
      postDataChunk
+ "'.");
   
});

    request

.addListener("end", function() {
      route
(handle, pathname, response, postData);
   
});}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Здесь, в основном, мы сделали три вещи: во-первых, определили, что ожидаем полученные данные в кодировке UTF-8, затем добавили слушатель для события «data», который шаг за шагом заполняет нашу новую переменную postData всякий раз, когда прибывает новая порция POST-данных, и далее — переходим к вызову нашего роутера в обратном вызове события end, чтобы убедиться, что вызов происходит, когда все POST-данные собраны.
Мы также передаём POST-данные в роутере, потому что они нам понадобятся в обработчиках запроса.

Добавление логирования в консоли для каждой порции полученных данных — возможно, плохая идея для конечного кода (мегабайты POST-данных, помните?), но это имеет смысл, чтобы посмотреть, что происходит.

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

Давайте добавим ещё больше крутизны в наше приложение.
На странице /upload мы будем показывать принятый контент.
Чтобы сделать это возможным, нам необходимо передавать postData в обработчики запроса. В router.js:

function route(handle, pathname, response, postData) {
  console
.log("About to route a request for " + pathname);
 
if (typeof handle[pathname] === 'function') {
    handle
[pathname](response, postData);
 
} else {
    console
.log("No request handler found for " + pathname);
    response
.writeHead(404, {"Content-Type": "text/plain"});
    response
.write("404 Not found");
    response
.end();
 
}
}

exports

.route = route;

И в requestHandlers.js мы включаем эти данные в нашем ответе обработчика запроса upload:

function start(response, postData) {
  console
.log("Request handler 'start' was called.");var body = '<html>'+
   
'<head>'+
   
'<meta http-equiv="Content-Type" content="text/html; '+
   
'charset=UTF-8" />'+
   
'</head>'+
   
'<body>'+
   
'<form action="/upload" method="post">'+
   
'<textarea name="text" rows="20" cols="60"></textarea>'+
   
'<input type="submit" value="Submit text" />'+
   
'</form>'+
   
'</body>'+
   
'</html>';

    response

.writeHead(200, {"Content-Type": "text/html"});
    response
.write(body);
    response
.end();
}function upload(response, postData) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("You've sent: " + postData);
  response
.end();
}

exports

.start = start;
exports
.upload = upload;

Вот и всё, теперь мы можем получить POST-данные и использовать их в наших обработчиках запроса.

И последнее по этой теме: то, что мы передаём в роутер и обработчики запроса, является полным телом нашего POST-запроса.
Мы, вероятно, захотим использовать индивидуальные поля, составляющие POST-данные, в нашем случае значение поля text.

Мы уже читали про модуль querystring, который поможет нам с этим:

var querystring = require("querystring");function start(response, postData) {
  console
.log("Request handler 'start' was called.");var body = '<html>'+
   
'<head>'+
   
'<meta http-equiv="Content-Type" content="text/html; '+
   
'charset=UTF-8" />'+
   
'</head>'+
   
'<body>'+
   
'<form action="/upload" method="post">'+
   
'<textarea name="text" rows="20" cols="60"></textarea>'+
   
'<input type="submit" value="Submit text" />'+
   
'</form>'+
   
'</body>'+
   
'</html>';

    response

.writeHead(200, {"Content-Type": "text/html"});
    response
.write(body);
    response
.end();
}function upload(response, postData) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("You've sent the text: "+
  querystring
.parse(postData).text);
  response
.end();
}

exports

.start = start;
exports
.upload = upload;

Это всё, что можно сказать про обработку POST-данных в рамках учебника для начинающих.

Обработка загрузки файлов

Давайте примемся за последний пункт нашего списка задач.
Мы планировали дать возможность пользователям загружать файлы картинок и отображать загруженные картинки в браузере.

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

Внешний модуль, который мы собираемся использовать, node-formidable от Felix Geisendörfer.
Этот модуль поможет нам абстрагироваться от мерзких деталей парсинга входящих файловых данных.
В конце концов, обработка входящих файлов это не что иное, как «просто» обработка POST-данных, но, в действительности, дьявол кроется в деталях, поэтому в нашем случае имеет смысл использовать готовое решение.

Чтобы использовать код Феликса, соответствующий модуль Node.js должен быть инсталлирован.
На борту Node.js есть собственный менеджер пакетов, называемый NPM.
Он позволяет нам инсталировать внешние модули Node.js в очень удобной форме.
С учетом установленного Node.js, всё сводится к

npm install formidable

в нашей командной строке. Если вы в конце увидели следующее:

npm info build Success: formidable@1.0.9
npm ok

…это значит — всё хорошо.

Модуль formidable теперь доступен в нашем коде — всё, что нужно, это просто запросить его как один из тех модулей, которые мы использовали ранее:

var formidable = require("formidable");

По сути, formidable делает форму, отправленную через HTTP POST, доступной для парсинга в Node.js.
Всё, что нам надо — это создать новый экземпляр объекта IncomingForm, который является абстракцией отправленной формы и может быть использован для парсинга объекта request нашего HTTP-сервера, для полей и файлов, отправленных через эту форму.

Пример кода со страницы проекта node-formidable показывает, как разные части сочетаются друг с другом:

var formidable = require('formidable'),
    http
= require('http'),
    sys
= require('sys');

http

.createServer(function(req, res) {
 
if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
   
// parse a file upload
   
var form = new formidable.IncomingForm();
    form
.parse(req, function(err, fields, files) {
      res
.writeHead(200, {'content-type': 'text/plain'});
      res
.write('received upload:nn');
      res
.end(sys.inspect({fields: fields, files: files}));
   
});
   
return;
 
}// show a file upload form
  res
.writeHead(200, {'content-type': 'text/html'});
  res
.end(
   
'<form action="/upload" enctype="multipart/form-data" '+
   
'method="post">'+
   
'<input type="text" name="title"><br>'+
   
'<input type="file" name="upload" multiple="multiple"><br>'+
   
'<input type="submit" value="Upload">'+
   
'</form>'
 
);
}).listen(8888);

Если вы поместите этот код в файл и исполните его посредством node, вы сможете отправлять простые формы, включая загрузку фото, и увидите, как организован объект files, который передавался в callback, определенном в вызове form.parse.

received upload:

{ fields: { title: 'Hello World' },
  files:
   { upload:
	  { size: 1558,
		path: '/tmp/1c747974a27a6292743669e91f29350b',
		name: 'us-flag.png',
		type: 'image/png',
		lastModifiedDate: Tue, 21 Jun 2011 07:02:41 GMT,
		_writeStream: [Object],
		length: [Getter],
		filename: [Getter],
		mime: [Getter] } } }

Чтобы выполнить последний пункт нашей задачи, мы должны включить логику парсинга форм модуля formidable в структуру нашего кода, плюс ко всему, надо разобраться, как отдавать контент загруженного файла (который сохранен в директории /tmp) браузеру.

Давайте сначала решим последнюю задачу: если имеется файл картинки на вашем локальном диске, что сделать, чтобы передать его браузеру?

Мы, очевидно, собираемся считать содержимое этого файла в наш Node.js-сервер, и неудивительно, что для этого имеется соответствующий модуль под названием fs.

Давайте добавим ещё один обработчик запроса для URL /show, который будет «захардкоженно» показывать содержимое файла /tmp/test.png.
Конечно же, имеет смысл в первую очередь поместить реальную png-картинку в этот каталог.

Мы собираемся изменить requestHandlers.js, как показано далее:

var querystring = require("querystring"),
    fs
= require("fs");function start(response, postData) {
  console
.log("Request handler 'start' was called.");var body = '<html>'+
   
'<head>'+
   
'<meta http-equiv="Content-Type" '+
   
'content="text/html; charset=UTF-8" />'+
   
'</head>'+
   
'<body>'+
   
'<form action="/upload" method="post">'+
   
'<textarea name="text" rows="20" cols="60"></textarea>'+
   
'<input type="submit" value="Submit text" />'+
   
'</form>'+
   
'</body>'+
   
'</html>';

    response

.writeHead(200, {"Content-Type": "text/html"});
    response
.write(body);
    response
.end();
}function upload(response, postData) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("You've sent the text: "+
  querystring
.parse(postData).text);
  response
.end();
}function show(response, postData) {
  console
.log("Request handler 'show' was called.");
  fs
.readFile("/tmp/test.png", "binary", function(error, file) {
   
if(error) {
      response
.writeHead(500, {"Content-Type": "text/plain"});
      response
.write(error + "n");
      response
.end();
   
} else {
      response
.writeHead(200, {"Content-Type": "image/png"});
      response
.write(file, "binary");
      response
.end();
   
}
 
});
}

exports

.start = start;
exports
.upload = upload;
exports
.show = show;

Также, надо преобразовать новый обработчик запроса в URL вида /show в файле index.js:

var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");var handle = {}
handle
["/"] = requestHandlers.start;
handle
["/start"] = requestHandlers.start;
handle
["/upload"] = requestHandlers.upload;
handle
["/show"] = requestHandlers.show;

server

.start(router.route, handle);

Перезапускаем сервер, открываем http://localhost:8888/show в браузере и видим картинку /tmp/test.png.

Хорошо. Всё, что нам надо теперь — это:

  • добавить поле для загрузки файлов в форму, находящуюся по адресу /start,
  • интегрировать node-formidable в обработчик запроса upload, чтобы сохранять загруженные файлы в /tmp/test.png,
  • внедрить загруженную картинку в HTML, отдаваемый по URL /upload.

Первый шаг — простой. Нам надо добавить тип кодировки multipart/form-data в нашу HTML-форму, удалить текстовое поле, добавить поле загрузки файла и поменять текст кнопки отправки формы на «Upload file».
Давайте просто сделаем это в файле requestHandlers.js:

var querystring = require("querystring"),
    fs
= require("fs");function start(response, postData) {
  console
.log("Request handler 'start' was called.");var body = '<html>'+
   
'<head>'+
   
'<meta http-equiv="Content-Type" '+
   
'content="text/html; charset=UTF-8" />'+
   
'</head>'+
   
'<body>'+
   
'<form action="/upload" enctype="multipart/form-data" '+
   
'method="post">'+
   
'<input type="file" name="upload">'+
   
'<input type="submit" value="Upload file" />'+
   
'</form>'+
   
'</body>'+
   
'</html>';

    response

.writeHead(200, {"Content-Type": "text/html"});
    response
.write(body);
    response
.end();
}function upload(response, postData) {
  console
.log("Request handler 'upload' was called.");
  response
.writeHead(200, {"Content-Type": "text/plain"});
  response
.write("You've sent the text: "+
  querystring
.parse(postData).text);
  response
.end();
}function show(response, postData) {
  console
.log("Request handler 'show' was called.");
  fs
.readFile("/tmp/test.png", "binary", function(error, file) {
   
if(error) {
      response
.writeHead(500, {"Content-Type": "text/plain"});
      response
.write(error + "n");
      response
.end();
   
} else {
      response
.writeHead(200, {"Content-Type": "image/png"});
      response
.write(file, "binary");
      response
.end();
   
}
 
});
}

exports

.start = start;
exports
.upload = upload;
exports
.show = show;

Замечательно. Следующий шаг — немного более сложный, конечно.
Первая проблема следующая: мы хотим обрабатывать загрузку файлов в нашем обработчике запроса upload, и тут надо будет передать объект request при вызове form.parse модуля node-formidable.

Но всё, что у нас есть — это объект response и массив postData.
Грустно.
Похоже, что придётся передавать каждый раз объект request из сервера в роутер и обработчик запроса.
Может быть, имеется более элегантное решение, но этот способ может делать работу уже сейчас.

Давайте полностью удалим всё, что касается postData в нашем сервере и обработчиках запроса — он нам не нужен для обработки загрузки файла и, мало того, — даже создает проблему: мы уже «поглотили» события data объекта request в сервере, а следовательно, form.parse, которому так же надо поглащать эти события, не сможет получить больше данных (потому что Node.js не буферизирует данные).

Начнём с server.js — удалим обработку postData и строку с request.setEncoding (node-formidable сам всё сделает) и передадим request в роутер:

var http = require("http");
var url = require("url");function start(route, handle) {
 
function onRequest(request, response) {
   
var pathname = url.parse(request.url).pathname;
    console
.log("Request for " + pathname + " received.");
    route
(handle, pathname, response, request);
 
}

  http

.createServer(onRequest).listen(8888);
  console
.log("Server has started.");
}

exports

.start = start;

Следующий — router.js — мы больше не передаём postData, а вместо этого передаём request:

function route(handle, pathname, response, request) {
  console
.log("About to route a request for " + pathname);
 
if (typeof handle[pathname] === 'function') {
    handle
[pathname](response, request);
 
} else {
    console
.log("No request handler found for " + pathname);
    response
.writeHead(404, {"Content-Type": "text/html"});
    response
.write("404 Not found");
    response
.end();
 
}
}

exports

.route = route;

Теперь объект request может быть использован в функции обработчика запроса upload.
node-formidable будет заниматься сохранением загруженного файла в локальный файл /tmp, но, конечно, мы сами должны сделать, чтобы этот файл переименовывался в /tmp/test.png.
Да, мы придерживаемся действительно простых вещей и принимаем, что могут загружаться только PNG-картинки.

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

Давайте добавим в requestHandlers.js код управления загрузкой файла и переименованием:

var querystring = require("querystring"),
	fs = require("fs"),
	formidable = require("formidable");

function start(response) {
  console.log("Request handler 'start' was called.");

  var body = '<html>'+
	'<head>'+
	'<meta http-equiv="Content-Type" '+
	'content="text/html; charset=UTF-8" />'+
	'</head>'+
	'<body>'+
	'<form action="/upload" enctype="multipart/form-data" '+
	'method="post">'+
	'<input type="file" name="upload" multiple="multiple">'+
	'<input type="submit" value="Upload file" />'+
	'</form>'+
	'</body>'+
	'</html>';

	response.writeHead(200, {"Content-Type": "text/html"});
	response.write(body);
	response.end();
}

function upload(response, request) {
  console.log("Request handler 'upload' was called.");

  var form = new formidable.IncomingForm();
  console.log("about to parse");
  form.parse(request, function(error, fields, files) {
	console.log("parsing done");

/* Возможна ошибка в Windows: попытка переименования уже существующего файла */
	fs.rename(files.upload.path, "/tmp/test.png", function(err) {
	  if (err) {
		fs.unlink("/tmp/test.png");
		fs.rename(files.upload.path, "/tmp/test.png");
	  }
	});
	response.writeHead(200, {"Content-Type": "text/html"});
	response.write("received image:<br/>");
	response.write("<img src='/show' />");
	response.end();
  });
}

function show(response) {
  console.log("Request handler 'show' was called.");
  fs.readFile("/tmp/test.png", "binary", function(error, file) {
	if(error) {
	  response.writeHead(500, {"Content-Type": "text/plain"});
	  response.write(error + "n");
	  response.end();
	} else {
	  response.writeHead(200, {"Content-Type": "image/png"});
	  response.write(file, "binary");
	  response.end();
	}
  });
}

exports.start = start;
exports.upload = upload;
exports.show = show;

Вот и всё. Перезапускаем сервер, и последний пункт нашей задачи реализован.
Выбираем локальную PNG-картинку с диска, загружаем на сервер и видим её на нашей веб-страничке.

Выводы и перспективы

Поздравляю, наша миссия выполнена!
Мы написали простое, уже полностью готовое web-приложение на Node.js.
Мы поговорили о server-side JavaScript, функциональном программировании, блокирующих и неблокирующих операциях, callback-ах, событиях, обычаях, внутренних и внешних модулях и о многом другом.

Конечно, имеется много вещей, которые мы не обсудили: как общаться с базой данных, как писать unit-тесты, как создавать внешние модули, с возможностью инсталяции через NPM или даже что-нибудь простое, например, как обрабатывать GET-запросы.

Но это судьба каждого учебника, предназначенного для начинающих — нельзя расказать о всех аспектах во всех деталях.

Хорошей новостью является то, что Node.js-сообщество — очень активное (думаю, даже гиперактивное от кофеина, но в хорошем смысле), имеется множество ресурсов, кроме этого, и множество мест, где можно получить ответы на ваши вопросы.
Node.js community wiki и the NodeCloud directory являются, возможно, лучшими отправными точками.

Node.js представляет среду выполнения кода на JavaScript, которая построена на основе движка JavaScript Chrome V8, который позволяет транслировать вызовы на языке JavaScript в машинный код. Node.js прежде всего предназначен для создания серверных приложений на языке JavaScript. Хотя также существуют проекты по написанию десктопных приложений (Electron) и даже по созданию кода для микроконтроллеров. Но прежде всего мы говорим о Node.js, как о платформе для создания веб-приложений.

Node.js является открытым проектом, исходники которого можно посмотреть на github.com.

Установка¶

Для загрузки перейдет на официальный сайт https://nodejs.org/en/. На главной странице мы сразу увидим две возможные опции для загрузки: самая последняя версия NodeJS и LTS-версия.

Установка Node.js

Загрузим последнюю версию. В моем случае это версия 11.1.0. Для Windows установщик представляет файл с расширением msi. После запуска откроется программа установщика:

Установка Node.js

После успешной установки вы можем ввести в командной строке/терминале команду node -v, и нам отобразится текущая версия node.js:

C:WINDOWSsystem32>node -v
v11.1.0

Версии node.js для других операционных систем наряду с исходниками можно найти по адресу https://nodejs.org/en/download/

Инструменты разработки¶

Для разработки под Node JS достаточно простейшего текстового редактора, в частности, Notepad++. Также можно использовать более продвинутые редакторы типа Atom, Sublime, Visual Studio Code, либо среды разработки, которые поддерживают работу с Node.JS, например, Visual Studio или WebStorm.

REPL¶

После установки NodeJS нам становится доступным такой инструмент как REPL. REPL (Read Eval Print Loop) представляет возможность запуска выражений на языке JavaScript в командной строке или терминале.

Так, запустим командную строку (на Windows) или терминал (на OS X или Linux) и введем команду node. После ввода этой команды мы можем выполнять различные выражения на JavaScript:

C:WINDOWSsystem32>node
> 2+6
8
>

Или используем какую-нибудь функцию JS:

> console.log("Hello NodeJS");
Hello NodeJS
undefined
>

Можно определять свои функции и затем их вызывать, например, возведение числа в квадрат:

> function sqrt(x){return x * x;}
undefined
>sqrt(5)
25
>

Если мы введем что-то неправильно, то REPL укажет об ошибке:

Консоль

Выполнение файла¶

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

console.log('Hello world')

В командной строке перейдем с помощью команды cd к каталогу helloapp, а затем выполним команду:

Данная команда выполнит код из файла app.js:

Консоль

Искусство Node

Введение в Node.js

Статья предназначена тем, кто хотя бы немного знаком с:

  • языками программирования, например, JavaScript, Ruby, Python, Perl, и т.д.
    Если вы пока не являетесь программистом, наверное, проще будет начать с чтения
    «JavaScript для котиков».
  • Git и GitHub. Это инструменты для совместной работы с открытым кодом, которые
    широко используются членами сообщества Node.js для обмена модулями.
    Вам достаточно знать азы. Вот три отличных самоучителя для начинающих:
    1, 2, 3

Написание этой короткой книги всё еще в процессе. Если она вам нравится,
пожалуйста, пожертвуйте доллар через gittip чтобы я мог оправдать
время, которое займет написание продолжения.

Содержание

  • Учим Node.js в интерактивном режиме
  • Философия Node.js
  • Базовые модули
  • Колбеки
  • События
  • Потоки
  • Модули и пакетный менеджер Node.js
  • Разработка на стороне клиента с использованием npm
  • Выбираем инструменты правильно

Учим Node.js в интерактивном режиме

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

NodeSchool.io

NodeSchool.io — это подборка интерактивных мастер-классов,
бесплатных и с открытым кодом, в которых изложены принципы Node.js и не только.

Мы научим вас Node.js — вводный мастер-класс от NodeSchool.io. Это
набор задач по программированию, который поможет вам познакомиться с
наиболее распространенными паттернами Node. Он доступен в виде консольной
программы.

программа learnyounode

Её можно установить с помощью пакетного менеджера Node:

# install
npm install learnyounode -g

# start the menu
learnyounode

Философия Node

Node.js — это проект с открытым исходным кодом, разработанный, чтобы
помочь вам создавать программы на JavaScript, которые могли бы
взаимодействовать с сетями, файловыми системами или другими I/O источниками
ввода/вывода. Вот и всё! Это простая и стабильная I/O платформа, на основе
которой предлагается создавать свои модули.

Какими бывают источники ввода/вывода? Вот схема моего приложения, построенного
на Node, которая отображает многообразие источников:

серверная схема

Ничего страшного, если в этой схеме вам понятны не все надписи. Её смысл в том,
чтобы показать, что один-единственный процесс Node.js (шестигранник в центре) может
служить посредником между различными конечными точками (оранжевые и фиолетовые
линии представляют ввод/вывод).

Обычно создание подобных систем подразумевает одно из двух:

  • сложный код, но с супербыстрым результатом (как в случае с написанием
    веб-сервера с нуля на С)
  • простой код, однако не слишком быстрый/надежный результат (например,
    когда кто-нибудь пытается загрузить файл весом 5Гб и сервер падает)

Цель Node.js состоит в достижении золотой середины: относительная простота
понимания и использования в сочетании со скоростью работы в большинстве
случаев применения.

Node.js не является:

  • Веб-фреймворком (вроде Rails или Django, хотя его можно использовать для
    создания таких вещей)
  • Языком программирования (для него используется JavaScript, Node.js не является
    языком сам по себе)

Node.js — это нечто среднее. Он:

  • Создан чтобы быть простым и, следовательно, относительно лёгким для
    понимания и использования
  • Будет полезным для программ, предусматривающих операции ввода/вывода,
    которые должны быть быстрыми и выдерживать большое количество соединений

На более низком уровне Node.js можно описать как инструмент для написания программ
двух типов:

  • Сетевых программ, использующих веб-протоколы: HTTP, TCP, UDP, DNS и SSL
  • Программ, производящих чтение и запись данных в/из файловых систем или
    локальных процессов/памяти.

Что следует понимать под «программами, предусматривающими операции
ввода/вывода»?
Вот некоторые наиболее типичные I/O источники:

  • Базы данных (напр. MySQL, PostgreSQL, MongoDB, Redis, CouchDB)
  • API (напр. Twitter, Facebook, рush-уведомления Apple)
  • Соединения HTTP/WebSocket (от пользователей веб-приложения)
  • Файлы (редактор изображений и видео, интернет-радио)

Node.js выполняет операции ввода/вывода асинхронно, что позволяет ему
проводить большое количество операций одновременно. Предположим, вы зашли в
заведение быстрого питания и заказали чизбургер, вы сделали свой заказ и
околачиваетесь поблизости пока его приготовят. В то же время персонал может
принимать заказы и готовить чизбургеры для других клиентов. Представьте, что
было бы, если бы вы остались ждать свой чизбургер у кассы, не давая остальным
посетителям в очереди сделать свой заказ до тех пор, пока ваш чизбургер не
будет готов! Это называется блокирующим вводом-выводом, так как все операции
ввода/вывода (приготовление чизбургеров) выполняются поочередно, в один момент
одна операция. Node, напротив, является неблокирующим, то есть может
готовить много чизбургеров одновременно.

Вот несколько занимательных вещей, которые можно легко сделать на Node,
благодаря её неблокирующей сущности:

  • Управление летающими квадрокоптерами
  • Написание IRC-ботов
  • Создание двуногих роботов, способных ходить

Базовые модули

Прежде всего я советовал бы вам установить Node.js на ваш компьютер. Самый простой
способ это сделать — зайти на nodejs.org и нажать Install.

У Node.js есть небольшая базовая группа модулей (их принято называть «ядро Node»),
представленных как открытые API, на основе которых следует писать программы. Для
работы с файловыми системами существует модуль fs, для сетей используются
такие модули как net (TCP), http и dgram (UDP).

В дополнение к fs и сетевым модулям, ядро Node.js содержит ряд других базовых
модулей. Есть модуль для асинхронной обработки DNS-запросов под названием dns,
модуль os для получения сведений об ОС, например, о расположении tmpdir,
модуль buffer для распределения бинарных участков памяти, несколько модулей
для анализа ссылок и путей (url, querystring, path) и т.д. Большинство
модулей, составляющих ядро, предназначены для обеспечения главного
предназначения Node: создания быстрых программ, взаимодействующих
с файловыми системами или сетями.

Node.js выполняет операции ввода/вывода с помощью колбеков,
событий, потоков и модулей. Если вы разберётесь с тем, как работают эти четыре
компонента, вы сможете взять любой модуль из ядра Node.js и более-менее чётко
понять как с ним взаимодействовать.

Колбеки

Если вы хотите понять как использовать Node, то нужно прежде всего разобраться
с этой темой. Колбеки в Node.js используются практически
везде. Они не были придуманы для Node, и являются всего лишь частью языка
JavaScript.

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

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

var myNumber = 1;
function addOne() { myNumber++ } // определение функции
addOne(); // выполнение функции
console.log(myNumber); // в консоль выводится 2

Здесь сначала определяется функция, а затем в следующей строке происходит её
вызов, без каких-либо отсрочек. Когда происходит вызов функции, она немедленно
прибавляет 1 к переменной, так что можно рассчитывать, что после вызова функции
значение переменной должно быть равно 2. Это то, чего мы ожидаем от синхронного
кода — он последовательно выполняется сверху вниз.

Однако в Node, в большинстве случаев, используется асинхронный код. Попробуем
считать число из файла под названием number.txt, используя Node:

var fs = require('fs'); // require является специальной функцией предусмотренной в Node
var myNumber = undefined; // мы пока не знаем значение переменной, так как оно хранится в файле
 
function addOne() {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents);
    myNumber++;
  })
}
 
addOne();
 
console.log(myNumber); // в консоль выводится undefined — эта строчка запускается перед выполнением readFile

Почему при выводе результата в консоль мы видим undefined? В этом
коде мы используем метод fs.readFile, который является асинхронным. Обычно все
взаимодействия с жёстким диском или сетью являются асинхронными. Если требуется
всего лишь получение доступа к памяти или выполнение каких-либо действий с
процессором, можно использовать синхронный подход. Причиной этому является то,
что операции ввода/вывода ооооочень и оооочень меееедленные. По приблизительным
оценкам, взаимодействие с жёстким диском примерно в 100,000 медленнее, чем
взаимодействие с памятью (напр. с оперативной памятью).

При запуске этой программы все функции объявляются немедленно, но не все
выполняются сразу. Это основополагающая вещь для понимания асинхронного
программирования. Когда происходит вызов addOne, она вызывает readFile и
переходит дальше к тому, что ещё может быть выполнено. Если выполнять нечего,
Node.js либо ждёт окончания текущих операций с файловыми системами/сетями или же
прекращает работу и выходит в командную строку.

Когда readFile заканчивает чтение файла (это может продолжаться от
миллисекунды до нескольких секунд или минут, в зависимости от скорости жесткого
диска), он запускает функцию doneReading и передает ей ошибку (если она есть)
и содержимое файла.

Выше мы получили undefined потому, что нигде в коде не прописано, что команда
console.log должна подождать с выводом переменной до завершения работы
команды readFile.

Если вы хотите, чтобы определённый код выполнялся снова и снова, или же в
какой-то отложенный момент, первым делом следует поместить его в функцию.
Затем эту функцию можно будет вызвать в любое время, когда вы захотите этот
код выполнить. Удобнее давать функциям осмысленные названия, говорящие об их
предназначении.

Колбеки — это функции, которые могут быть выполнены позже. Ключом к пониманию
колбеков является осознание того, что они используются, когда время окончания
какой-либо асинхронной операции неизвестно, однако известно место её окончания —
последняя строчка асинхронной функции! Порядок сверху-вниз, в котором объявлены
функции, не играет особой роли, в отличие от их логической/иерархичной вложенности.
Сначала вы разбиваете код на функции, а затем используете колбеки для объявления,
что запуск одной функции зависит от окончания другой.

Метод fs.readFile, предлагаемый в Node, является асинхронным, и
иногда его выполнение затягивается на длительное время. Вот что он делает:
он обращается к операционной системе, которая, в свою очередь, обращается к
файловой системе, которая живёт на жёстком диске, вращающемся со скоростью выше
или ниже тысячи оборотов в минуту. Затем с помощью лазера считываются данные
и отсылаются назад в программу тем же путём. Вы передаёте readFile колбек,
который он вызовет после получения данных из файловой системы. Он помещает
полученные данные в javascript-переменную и вызывает колбек с этой переменной
в качестве аргумента. В этом случае переменная носит название fileContents,
так как в неё помещено содержимое прочитанного файла.

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

Давайте поместим нашу команду console.log в функцию и добавим её в код в
качестве колбека.

var fs = require('fs');
var myNumber = undefined;
 
function addOne(callback) {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents);
    myNumber++;
    callback();
  });
}
 
function logMyNumber() {
  console.log(myNumber);
}
 
addOne(logMyNumber);

Теперь функции logMyNumber можно передать аргумент, который станет переменной
callback внутри функции addOne. После завершения работы readFile будет
вызвана переменная callback (callback()). Так как вызываться могут только
функции, если попробовать вызвать что-либо кроме функции, мы получим ошибку.

Когда в JavaScript происходит вызов функции, код внутри этой функции немедленно
выполняется. В нашем случае будет выполнена команда вывода в консоль, поскольку
callback по сути является logMyNumber. Помните, что если просто объявить
функцию — она не будет выполнена. Для выполнения функцию нужно вызвать.

Чтобы проанализировать этот пример еще подробней, рассмотрим последовательность
событий, которые происходят при выполнении этой программы:

  1. Сначала код анализируется, а это означает, что если будут обнаружены
    синтаксические ошибки — программа работать не будет. На этом этапе
    объявляются четыре компонента: fs, myNumber, addOne и logMyNumber.
    Обратите внимание, что происходит только объявление, вызов каких-либо функций пока не производится.
  2. При выполнении последней строчки нашей программы, вызывается addOne,
    ей передается функция logMyNumber в качестве callback, которую нам
    нужно вызвать после завершения addOne. Это немедленно запускает асинхронную
    функцию fs.readFile. Эта часть программы занимает много времени.
  3. Так как больше ему заняться нечем, Node.js пребывает в режиме ожидания пока не
    завершится работа readFile. Если бы какие-нибудь задачи требовали выполнения в
    этот промежуток времени, Node.js занялся бы их выполнением.
  4. readFile заканчивает работу и вызывает колбек doneReading, который,
    в свою очередь, увеличивает число на единицу и немедленно вызывает колбек —
    logMyNumber, переданный в addOne.

Наверное, больше всего сбивает с толку то, что с функциями можно обращаться как
с простыми объектами, хранить их в переменных и передавать туда-сюда под разными
именами. Чтобы ваш код мог прочитать кто-нибудь кроме вас, важно давать
переменным простые и наглядные названия. В общем, если вы видите в программе на
Node.js переменную вроде callback или cb, можно предположить, что это колбек.

Возможно, вы слышали термины «событийно-ориентированное программирование» или
«событийный цикл». Они описывают процесс выполнения readFile. Сначала Node
запускает операцию readFile, затем ждёт пока readFile вышлет ему событие,
означающее её завершение. В процессе ожидания Node.js может проверить
состояние других процессов. У Node.js есть список операций, которые были запущены,
но от которых пока не получен ответ, он перебирает их снова и снова, проверяя не
были ли они завершены. После окончания работы они «обрабатываются»,
например, происходит запуск колбека, привязанных к завершению
их работы.

Вот схематическая версия кода, иллюстрирующая приведенный выше пример:

function addOne(thenRunThisFunction) {
  waitAMinute(function waitedAMinute() {
    thenRunThisFunction();
  });
}
 
addOne(function thisGetsRunAfterAddOneFinishes() {});

Представьте, что у вас есть три асинхронные функции a, b и c. Выполнение
каждой из них занимает минуту, и после завершения каждой происходит запуск
колбека (которому передается первый аргумент). Если вы хотите сказать Node.js
«запусти a, после её завершения выполни b, после завершения b запусти c»
это выглядело бы так:

a(function() {
  b(function() {
    c();
  });
});

При выполнении этого кода, немедленно запустится a, через минуту она закончит
работу и вызовет b, которая закончится еще через минуту и вызовет c, и,
наконец, 3 минуты спустя после начала выполнения Node.js завершит обработку кода,
так как больше задач не останется. Конечно, можно было придумать более
изящные способы записать этот пример, однако смысл состоит в том, что если у вас
есть код, который должен подождать, пока будет завершено выполнение другого
асинхронного кода, то эту зависимость следует выразить, поместив код в колбек.

Структура Node.js требует от разработчика нелинейного мышления. Взгляните на этот
список операций:

  1. чтение файла
  2. обработка файла

Если бы вам пришлось превратить его в код, у вас получилось бы следующее:

var file = readFile();
processFile(file);

Такой линейный (пошаговый, упорядоченный) код не соответствует тому, как
работает Node. Если начать обработку такого кода, readFile и processFile
выполнялись бы одновременно. Это бессмысленно, так как выполнение readFile
займет много времени. Вместо этого вам нужно указать, что функция processFile
должна быть запущена после завершения readFile. Именно для этого и существуют
колбеки. Благодаря особенностям JavaScript, эту зависимость можно записать
несколькими разными способами:

var fs = require('fs');
fs.readFile('movie.mp4', finishedReading);
 
function finishedReading(error, movieData) {
  if (error) return console.error(error);
  // выполнение действий с movieData
}

Однако можно написать и такую структуру кода, и он все так же будет работать:

var fs = require('fs');
 
function finishedReading(error, movieData) {
  if (error) return console.error(error);
  // выполнение действий с movieData
}
 
fs.readFile('movie.mp4', finishedReading);

Или даже так:

var fs = require('fs');
 
fs.readFile('movie.mp4', function finishedReading(error, movieData) {
  if (error) return console.error(error);
  // выполнение действий с movieData
});

События

Если вам нужен модуль событий, в Node.js вы можете воспользоваться так называемым
«генератором событий», который используется во всех Node.js API, которые что-либо
генерируют.

События, более известные как паттерн «наблюдатель» или «издатель/подписчик»,
являются широко распространённым паттерном в программировании. В то время, как колбеки
представляют собой связь «один к одному» между тем, что ожидает колбек и тем,
что его вызывает, события представляют собою такую же связь, только между многими API.

Проще всего представить себе принцип работы событий как подписку на нечто. Они
позволяют указать «когда Х, сделай Y», тогда как простые колбеки указывают
только «сделай Х, затем Y».

Вот несколько типичных случаев использования событий вместо колбеков:

  • Чат, в котором вы хотите транслировать сообщения для многих пользователей
  • Игровой сервер, которому нужно знать когда новые игроки присоединяются,
    отсоединяются, двигаются, стреляют и прыгают
  • Игровой движок, в котором вы хотите предусмотреть для разработчиков игры
    возможность подписываться на события вроде .on('jump', function() {})
  • Веб-сервер низкого уровня, на котором нужно предоставить API для простого
    подключения к происходящим событиям вроде .on('incomingRequest') или
    .on('serverError')

Если бы мы попробовали написать модуль, подключающийся к серверу чата, используя
только колбеки, он бы выглядел так:

var chatClient = require('my-chat-client');
 
function onConnect() {
  // подтверждение подключения в интерфейсе
}
 
function onConnectionError(error) {
  // уведомление пользователя об ошибке
}
 
function onDisconnect() {
 // уведомление пользователя об отключении
}
 
function onMessage(message) {
 // отображение в интерфейсе сообщения из чата
}
 
chatClient.connect(
  'http://mychatserver.com',
  onConnect,
  onConnectionError,
  onDisconnect,
  onMessage
);

Как видите, такой способ очень громоздкий, так как функции .connect нужно
передать большое количество функций в определённом порядке. Написание того же с
использованием событий выглядело бы так:

var chatClient = require('my-chat-client').connect();
 
chatClient.on('connect', function() {
  // подтверждение подключения в интерфейсе
});
 
chatClient.on('connectionError', function() {
  // уведомление пользователя об ошибке
});
 
chatClient.on('disconnect', function() {
  // уведомление пользователя об отключении
});
 
chatClient.on('message', function() {
  // отображение в интерфейсе сообщения из чата
});

Это похоже на способ с использованием колбека, однако добавлен метод .on,
который подписывает колбек на событие. Это значит, что вы можете выбирать
на какие события в chatClient нужно подписаться. Также можно подписаться
на одно и то же событие несколько раз, используя разные колбеки:

var chatClient = require('my-chat-client').connect();
chatClient.on('message', logMessage);
chatClient.on('message', storeMessage);
 
function logMessage(message) {
  console.log(message);
}
 
function storeMessage(message) {
  myDatabase.save(message);
}

Потоки

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

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

Для изучения потоков в Node.js есть два отличных ресурса. Один из них —
stream-adventure, второй — справочный ресурс под названием «Справочник по
потокам».

Справочник по потокам

Справочник по потокам — это руководство, похожее на текущее, которое
содержит ссылки на всё, что вам следует знать о потоках.

stream-handbook

Модули

Ядро Node.js состоит примерно из двух дюжин модулей, некоторые из них более
низкоуровневые, такие как events и stream, другие — более высокоуровневые,
такие как http и crypto.

Такая структура была придумана специально. Ядро Node.js должно быть небольшим, а
модули, его составляющие, должны являться кросс-платформенными инструментами для
работы со всеми распространёнными протоколами и форматами ввода/вывода.

Для всего остального существует пакетный менеджер Node. Кто угодно может
создать новый модуль Nodе с дополнительными функциональными возможностями и
добавить его в npm. На момент написания этой статьи npm насчитывает
34,000 модулей.

Как найти модуль

Представьте, что вам нужно переконвертировать файлы PDF в TXT. Начать лучше
всего с команды npm search pdf:

поиск pdf

Результатов масса! npm довольно популярен, и в большинстве случаев
вы сможете найти несколько потенциальных решений. Если пройтись по модулям и
сократить количество результатов (отфильтровав, например, модули
для генерации PDF), получим следующие:

  • hummus — модуль управления pdf на с++
  • mimeograph — api на основе совмещения инструментов (poppler, tesseract,
    imagemagick и др.)
  • pdftotextjs — оболочка для утилиты pdftotext
  • pdf-text-extract — ещё одна оболочка для pdftotext
  • pdf-extract — оболочка для pdftotext, pdftk, tesseract, ghostscript
  • pdfutils — оболочка для библиотеки poppler
  • scissors — оболочка для pdftk, ghostscript с api высокого уровня
  • textract — оболочка для pdftotext
  • pdfiijs — конвертер pdf в инвертированный индекс с использованием
    textiijs и poppler
  • pdf2json — конвертер pdf в json на чистом js

Функциональные возможности многих модулей пересекаются, однако представляют
альтернативные API, и большинство из них требует установки внешних зависимостей
(таких как apt-get install poppler).

Вот несколько разных способов сравнивать модули:

  • pdf2json является единственным написанным на чистом JavaScript, что делает
    его самым простым в установке, особенно на маломощных устройствах, вроде
    одноплатного компьютера Raspberry Pi или на Windows, где внутренний код может
    быть не кроссплатформенным.
  • каждый из модулей вроде mimeograph, hummus и pdf-extract объединяют в
    себе несколько модулей низшего уровня для предоставления высокоуровневого API
  • множество модулей является надстройками для pdftotext/poppler, консольных
    инструментов unix

Давайте сравним pdftotextjs и pdf-text-extract, они оба являются
обертками для pdftotext.

pdf-модули

Оба модуля:

  • были обновлены сравнительно недавно
  • имеют собственные репозитории на github (это очень важно!)
  • имеют описания README
  • еженедельно устанавливаются некоторым количеством пользователей
  • имеют свободную лицензию (кто-угодно может их использовать)

По package.json и статистике модуля трудно понять какой
из них лучше выбрать. Давайте сравним описания в README:

pdf-описания

У обоих простые описания, указан статус сборки, есть инструкции по установке,
понятные примеры и инструкции для проведения тестирования. Отлично! Однако какой
же выбрать? Давайте сравним код:

pdf-код

pdftotextjs состоит из около 110 строчек кода, а pdf-text-extract — из 40,
однако, по сути, оба сводятся к следующей строчке:

var child = shell.exec('pdftotext ' + self.options.additional.join(' '));

Делает ли это один из них лучше другого? Трудно сказать. Важно, собственно,
вчитаться в код и сделать свой собственный вывод. Когда вы встречаете
удобные и полезные модули, используйте npm star modulename, чтобы оставить в
пакетном менеджере свой отзыв о модулях, которые вам понравились.

Организация процесса разработки с использованием модулей

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

Многие пакетные менеджеры устанавливают всё глобально. Например, если выполнить
команду apt-get install couchdb в Debian Linux, она попытается установить
последнюю стабильную версию CouchDB. Если вы хотите установить CouchDB как
зависимость для другой программы, и эта программа требует более раннюю версию
CouchDB, вам придётся деинсталлировать более новую версию CouchDB и затем
установить более старую. Установить обе версии нельзя, так как Debian умеет
устанавливать что-либо только в одном месте.

Так обстоят дела не только в Debian. Точно так же работает большинство пакетных
менеджеров для различных языков программирования. Чтобы решить проблему с
глобальной установкой зависимостей, описанную выше, было разработано виртуальное
окружение, такое как virtualenv для Python и bundler для Ruby. Они
разбивают ваше окружение на множество виртуальных, по одному на каждый проект,
однако внутри виртуального окружения зависимости устанавливаются всё так же
глобально. Виртуальные окружения не всегда решают проблему, иногда они её
приумножают, добавляя новые уровни сложности.

При использовании пакетного менеджера Node.js устанавливать глобальные модули
крайне не рекомендуется. Точно так же, как в программах на JavaScript не
рекомендуется использовать глобальные переменные, так же и с установкой
глобальных модулей (разве что вам нужно чтобы модуль с загрузочным двоичным
кодом отображался в глобальном PATH, однако это требуется не всегда —
подробнее об этом позже).

Как работает require

Когда вы в Node.js вызываете require('some_module'), происходит следующее:

  1. Если в текущей папке есть файл с названием some_module.js, Node.js его
    загрузит, в противном случае:
  2. Node.js проверит текущую папку на наличие папки node_modules с папкой
    some_module внутри неё
  3. Если он её не найдет, он поднимется на одну папку выше и повторит шаг 2

Этот цикл повторяется, пока Node.js не достигнет корневой папки файловой системы,
после чего он проверит наличие папок с глобальными модулями (например,
/usr/local/node_modules на Mac OS) и если some_module опять не будет найден,
он сгенерирует исключение.

Вот визуальный пример:

модули-01

Когда текущей рабочей директорией является subsubfolder и происходит вызов
require('foo'), Node.js ищет папку с названием subsubsubfolder/node_modules. В
этом случае он её не найдет, так как папка по ошибке названа my_modules. Затем
Node.js поднимается на одну папку выше и повторяет попытку, то есть он ищет
subfolder_B/node_modules, которая также не существует. Третья попытка, тем не
менее, оказывается удачной, поскольку folder/node_modules существует и
содержит папку с названием foo внутри. Если бы foo в ней не было, Node
продолжил бы поиск вверх по дереву директорий.

Обратите внимание, что если бы Node.js был вызван в папке subfolder_B, он ни за
что бы не нашел subfolder_A/node_modules, так как он может увидеть
folder/node_modules только по пути вверх по дереву директорий.

Одним из преимуществ подхода пакетного менеджера Node.js является то, что модули
могут устанавливать зависимые модули конкретных рабочих версий. В данном случае
очень популярен модуль foo — он установлен трижды, по одному в папке каждого
родительского модуля. Причиной может быть то, что для каждого
модуля требуется другая версия foo, например для folder нужен
foo@0.0.1, для subfolder_Afoo@0.2.1 и т.д.

Вот что произойдет, если исправить ошибку в названии папки с my_modules на
более правильное node_modules:

модули-02

Чтобы проверить, какой именно модуль будет загружен Node, можно использовать
команду require.resolve('some_module'), которая отобразит путь к модулю,
найденному Node.js в процессе прохода вверх по дереву директорий.
require.resolve может пригодиться для перепроверки того, что будет загружен
именно тот модуль, который вы ожидаете. Иногда оказывается, что существует ещё
одна версия того же модуля ближе к текущей рабочей директории, чем тот, который
вы хотели бы загрузить.

Как написать модуль

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

Наипростейший модуль из возможных

Модули Node.js исключительно мало весят. Вот один из наипростейших модулей:

package.json:

{
  "name": "number-one",
  "version": "1.0.0"
}

index.js:

По умолчанию Node.js пытается загрузить module/index.js, когда вы запрашиваете
require('module'). Никакое другое имя работать не будет, если вы не пропишите
путь к нему в package.json в поле main.

Поместите оба файла в папку number-one (id в package.json должно
соответствовать названию папки), и вы получите работающий Node-модуль.

Вызов функции require('number-one') возвращает то значение, которое указано
для module.exports внутри модуля:

простой модуль

Еще быстрее создать модуль можно с помощью этих команд:

mkdir my_module
cd my_module
git init
git remote add git@github.com:yourusername/my_module.git
npm init

Выполнение npm init создаст валидный package.json и, если вы запустите его
в существующем репозитории git, он также автоматически добавит в
package.json поле repositories!

Добавление зависимостей

Модуль может содержать перечень каких-либо других модулей из npm
или GitHub в поле dependencies в package.json. Чтобы установить модуль
request как новую зависимость и автоматически добавить его в package.json,
выполните эту команду в корневой директории вашего модуля:

npm install --save request

Это установит копию request в ближайшую папку node_modules и сделает
package.json примерно таким:

{
  "id": "number-one",
  "version": "1.0.0",
  "dependencies": {
    "request": "~2.22.0"
  }
}

По умолчанию, npm install установит последнюю официальную версию модуля.

Разработка на стороне клиента с использованием npm

Существует популярное заблуждение, что, поскольку в названии npm указано слово
«Node», он должен использоваться только для JS модулей на стороне сервера. Это
абсолютно не верно! Название пакетного менеджера Node.js подразумевает, что он
отвечает за управление модулями, которые Node.js упаковывает для вас в пакеты.
Модули сами по себе могут быть какими угодно — они всего лишь представляют из
себя папку с файлами, упакованную в архив .tar.gz, и файл package.json, в
котором указана версия модуля и перечень модулей, являющихся его зависимостями
(а также номера их версий, чтобы рабочие версии устанавливались автоматически).
Зависимости модулей являются обычными модулями, которые также могут иметь
зависимости — и так до бесконечности.

browserify — это утилита, написанная на Node, которая пытается
переконвертировать любой Node-модуль так, чтобы он мог быть запущен в браузере.
Не все модули удастся запустить таким образом (например, браузеры нельзя
использовать для таких задач как хостинг HTTP-сервера), но многие модули
действительно работают.

Чтобы попробовать npm в браузере, используйте RequireBin, приложение,
созданное мной на основе Browserify-CDN, в основе которого лежит
browserify, но для вывода используется HTTP (вместо командной строки, которая
обычно используется для browserify).

Скопируйте этот код в RequireBin и нажмите кнопку предварительного просмотра:

var reverse = require('ascii-art-reverse');
 
// делает HTML консоль видимой
require('console-log').show(true);
 
var coolbear =
  "    ('-^-/')  n" +
  "    `o__o' ]  n" +
  "    (_Y_) _/  n" +
  "  _..`--'-.`, n" +
  " (__)_,--(__) n" +
  "     7:   ; 1 n" +
  "   _/,`-.-' : n" +
  "  (_,)-~~(_,) n";
 
setInterval(function() { console.log(coolbear) }, 1000);
 
setTimeout(function() {
  setInterval(function() { console.log(reverse(coolbear)) }, 1000);
}, 500);

Или взгляните на более сложный пример(не стесняйтесь поиграть с
кодом, чтобы посмотреть что получается):

requirebin

Выбираем инструменты правильно

Как любой хороший инструмент, Node.js лучше всего подходит для конкретного
набора задач. Например, Rails, популярный веб-фреймворк, идеально подходит для
сложной бизнес-логики, т.е. использования кода для представления реальных
бизнес объектов, вроде счетов, ссуд и оборотного капитала. Хотя создание
подобных вещей с помощью Node.js является технически возможным, без проблем не
обойдётся, так как Node.js придуман для решения проблем ввода/вывода и не слишком
подходит для использования в сфере «бизнес-логики». Каждый инструмент
предназначен для решения своих задач. Надеюсь, это руководство поможет вам
обрести интуитивное понимание сильных сторон Node.js и того, в каких случаях он
может быть вам полезен.

Что не входит в компетенцию Node?

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

Веб-фреймворки

Существует ряд веб-фреймворков, построенных на основе Node.js (под фреймворком
следует понимать пакет программ, который пытается решить некую высокоуровневую
задачу вроде моделирования бизнес-логики), однако Node.js веб-фреймворком не
является. Веб-фреймворки, написанные на Node, не всегда разделяют его
подход к наращиванию сложности, абстрактности и компромиссности, и могут иметь
другие приоритеты.

Языковой синтаксис

Node.js использует JavaScript без каких-либо изменений. Феликс Гейзендорфер (Felix
Geisendorfer) составил хорошее описание «стиля Node» здесь.

Языковые абстракции

При любой возможности Node.js использует самый простой из доступных способов
выполнить задачу. Чем навороченнее ваш JavaScript, тем больше сложностей и
компромиссов вам приходится использовать. Программирование — непростое занятие,
особенно когда речь идёт о JS, у которого на каждую проблему по 1000 решений.
Именно поэтому Node.js всегда пытается выбрать самое простое и универсальное из
них. Если вы заняты задачей, для которой требуется сложное решение, и недовольны
«простенькими вариантами», которые предлагает Node, вы можете без проблем решить
её для своего приложения или модуля, используя любые абстракции на ваш вкус.

Прекрасным примером этому служит использование в Node.js колбеков.
Изначально в Node.js проводились эксперименты с элементом под названием «промисы»,
которые предусматривали ряд приспособлений для того, чтобы асинхронный код
выглядел более линейным. Они были изъяты из ядра Node.js по нескольким причинам:

  • Их сложнее использовать, чем колбеки
  • Их можно установить ввиде пакета из npm

Рассмотрим самое простое и базовое действие, которое производит Node: чтение
файла. В процессе чтения файла вы хотите знать когда происходят ошибки,
например, когда жесткий диск глохнет посередине процесса. Если бы промисы
использовались в Node, дерево кода должно было бы выглядеть так:

fs.readFile('movie.mp4')
  .then(function(data) {
    // проведение действий с данными
  })
  .error(function(error) {
    // обработка ошибки
  });

Это всё усложняет, и не каждому такое понравится. Вместо двух отдельных функций
Node.js использует один-единственный колбек. Он следует таким
правилам:

  • Если ошибка не произошла, null передаётся в качестве первого аргумента
  • Когда происходит ошибка, она передаётся в качестве первого аргумента
  • Остальными аргументами может быть что-угодно (обычно это данные или отклики,
    так как Node.js отвечает, в основном, за чтение или запись)

Отсюда стиль колбеков Node:

fs.readFile('movie.mp4', function(err, data) {
  // обработка ошибки, выполнение действий с данными
});

Потоки исполнения/волокна/достижение параллельности без использования событий

Примечание: если эти названия ни о чём вам не говорят, возможно, вам будет проще
освоить Node, так как избавление от знаний требует столько же усилий, сколько их
получение.

Node.js использует потоки исполнения для ускорения процессов, но не отображает их
пользователю. Если вы технически продвинутый пользователь, интересующийся почему
Node.js был реализован именно таким образом, вам 100% следует почитать о структуре
библиотеки libuv, I/O-слое C++, на котором построен Node.

Логотип компании «Одноклассники»

Статья переведена благодаря спонсорской поддержке компании «Одноклассники».

Node.js tutorial in Visual Studio Code

Node.js is a platform for building fast and scalable server applications using JavaScript. Node.js is the runtime and npm is the Package Manager for Node.js modules.

Visual Studio Code has support for the JavaScript and TypeScript languages out-of-the-box as well as Node.js debugging. However, to run a Node.js application, you will need to install the Node.js runtime on your machine.

To get started in this walkthrough, install Node.js for your platform. The Node Package Manager is included in the Node.js distribution. You’ll need to open a new terminal (command prompt) for the node and npm command-line tools to be on your PATH.

To test that you have Node.js installed correctly on your computer, open a new terminal and type node --version and you should see the current Node.js version installed.

Linux: There are specific Node.js packages available for the various flavors of Linux. See Installing Node.js via package manager to find the Node.js package and installation instructions tailored to your version of Linux.

Windows Subsystem for Linux: If you are on Windows, WSL is a great way to do Node.js development. You can run Linux distributions on Windows and install Node.js into the Linux environment. When coupled with the WSL extension, you get full VS Code editing and debugging support while running in the context of WSL. To learn more, go to Developing in WSL or try the Working in WSL tutorial.

Hello World

Let’s get started by creating the simplest Node.js application, «Hello World».

Create an empty folder called «hello», navigate into and open VS Code:

mkdir hello
cd hello
code .

Tip: You can open files or folders directly from the command line. The period ‘.’ refers to the current folder, therefore VS Code will start and open the Hello folder.

From the File Explorer toolbar, press the New File button:

File Explorer New File

and name the file app.js:

File Explorer app.js

By using the .js file extension, VS Code interprets this file as JavaScript and will evaluate the contents with the JavaScript language service. Refer to the VS Code JavaScript language topic to learn more about JavaScript support.

Create a simple string variable in app.js and send the contents of the string to the console:

var msg = 'Hello World';
console.log(msg);

Note that when you typed console. IntelliSense on the console object was automatically presented to you.

console IntelliSense

Also notice that VS Code knows that msg is a string based on the initialization to 'Hello World'. If you type msg. you’ll see IntelliSense showing all of the string functions available on msg.

string IntelliSense

After experimenting with IntelliSense, revert any extra changes from the source code example above and save the file (⌘S (Windows, Linux Ctrl+S)).

Running Hello World

It’s simple to run app.js with Node.js. From a terminal, just type:

node app.js

You should see «Hello World» output to the terminal and then Node.js returns.

Integrated Terminal

VS Code has an integrated terminal which you can use to run shell commands. You can run Node.js directly from there and avoid switching out of VS Code while running command-line tools.

View > Terminal (⌃` (Windows, Linux Ctrl+`) with the backtick character) will open the integrated terminal and you can run node app.js there:

integrated terminal

For this walkthrough, you can use either an external terminal or the VS Code integrated terminal for running the command-line tools.

Debugging Hello World

As mentioned in the introduction, VS Code ships with a debugger for Node.js applications. Let’s try debugging our simple Hello World application.

To set a breakpoint in app.js, put the editor cursor on the first line and press F9 or click in the editor left gutter next to the line numbers. A red circle will appear in the gutter.

app.js breakpoint set

To start debugging, select the Run and Debug view in the Activity Bar:

Run icon

You can now click Debug toolbar green arrow or press F5 to launch and debug «Hello World». Your breakpoint will be hit and you can view and step through the simple application. Notice that VS Code displays a different colored Status Bar to indicate it is in Debug mode and the DEBUG CONSOLE is displayed.

hello world debugging

Now that you’ve seen VS Code in action with «Hello World», the next section shows using VS Code with a full-stack Node.js web app.

Note: We’re done with the «Hello World» example so navigate out of that folder before you create an Express app. You can delete the «Hello» folder if you want as it is not required for the rest of the walkthrough.

An Express application

Express is a very popular application framework for building and running Node.js applications. You can scaffold (create) a new Express application using the Express Generator tool. The Express Generator is shipped as an npm module and installed by using the npm command-line tool npm.

Tip: To test that you’ve got npm correctly installed on your computer, type npm --help from a terminal and you should see the usage documentation.

Install the Express Generator by running the following from a terminal:

npm install -g express-generator

The -g switch installs the Express Generator globally on your machine so you can run it from anywhere.

We can now scaffold a new Express application called myExpressApp by running:

express myExpressApp --view pug

This creates a new folder called myExpressApp with the contents of your application. The --view pug parameters tell the generator to use the pug template engine.

To install all of the application’s dependencies (again shipped as npm modules), go to the new folder and execute npm install:

cd myExpressApp
npm install

At this point, we should test that our application runs. The generated Express application has a package.json file which includes a start script to run node ./bin/www. This will start the Node.js application running.

From a terminal in the Express application folder, run:

npm start

The Node.js web server will start and you can browse to http://localhost:3000 to see the running application.

Your first Node Express App

Great code editing

Close the browser and from a terminal in the myExpressApp folder, stop the Node.js server by pressing CTRL+C.

Now launch VS Code:

code .

Note: If you’ve been using the VS Code integrated terminal to install the Express generator and scaffold the app, you can open the myExpressApp folder from your running VS Code instance with the File > Open Folder command.

The Node.js and Express documentation does a great job explaining how to build rich applications using the platform and framework. Visual Studio Code will make you more productive in developing these types of applications by providing great code editing and navigation experiences.

Open the file app.js and hover over the Node.js global object __dirname. Notice how VS Code understands that __dirname is a string. Even more interesting, you can get full IntelliSense against the Node.js framework. For example, you can require http and get full IntelliSense against the http class as you type in Visual Studio Code.

http IntelliSense

VS Code uses TypeScript type declaration (typings) files (for example node.d.ts) to provide metadata to VS Code about the JavaScript based frameworks you are consuming in your application. Type declaration files are written in TypeScript so they can express the data types of parameters and functions, allowing VS Code to provide a rich IntelliSense experience. Thanks to a feature called Automatic Type Acquisition, you do not have to worry about downloading these type declaration files, VS Code will install them automatically for you.

You can also write code that references modules in other files. For example, in app.js we require the ./routes/index module, which exports an Express.Router class. If you bring up IntelliSense on index, you can see the shape of the Router class.

Express.Router IntelliSense

Debug your Express app

You will need to create a debugger configuration file launch.json for your Express application. Click on Run and Debug in the Activity Bar (⇧⌘D (Windows, Linux Ctrl+Shift+D)) and then select the create a launch.json file link to create a default launch.json file. Select the Node.js environment by ensuring that the type property in configurations is set to "node". When the file is first created, VS Code will look in package.json for a start script and will use that value as the program (which in this case is "${workspaceFolder}\bin\www) for the Launch Program configuration.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceFolder}\bin\www"
    }
  ]
}

Save the new file and make sure Launch Program is selected in the configuration dropdown at the top of the Run and Debug view. Open app.js and set a breakpoint near the top of the file where the Express app object is created by clicking in the gutter to the left of the line number. Press F5 to start debugging the application. VS Code will start the server in a new terminal and hit the breakpoint we set. From there you can inspect variables, create watches, and step through your code.

Debug session

Deploy your application

If you’d like to learn how to deploy your web application, check out the Deploying Applications to Azure tutorials where we show how to run your website in Azure.


Next steps

There is much more to explore with Visual Studio Code, please try the following topics:

  • Settings — Learn how to customize VS Code for how you like to work.
  • Debugging — This is where VS Code really shines.
  • Video: Getting started with Node.js debugging — Learn how to attach to a running Node.js process.
  • Node.js debugging — Learn more about VS Code’s built-in Node.js debugging.
  • Debugging recipes — Examples for scenarios like client-side and container debugging.
  • Tasks — Running tasks with Gulp, Grunt and Jake. Showing Errors and Warnings.

2/2/2023

Понравилась статья? Поделить с друзьями:
  • Как прочитать смарт карту в windows
  • Как работать с nmap на windows
  • Как прочитать разделы linux в windows 10
  • Как работать с linux используя windows
  • Как прочитать раздел linux в windows