Начало работы с Node: введение в API, HTTP и ES6+ JavaScript

Опубликовано: 2022-03-10
Краткое резюме ↬ Введение в процесс разработки серверных веб-приложений — обсуждение передовых функций JavaScript ES6+, протокола передачи гипертекста, работы с API и JSON, а также использование Node.js для создания быстрых и масштабируемых серверных приложений.

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

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

Мы попытаемся ответить на все эти и другие вопросы в этой серии статей, подробно изучая внутреннюю работу Node, узнавая о протоколе передачи гипертекста, API и JSON, а также создавая собственный API книжной полки с использованием MongoDB, Express, Lodash, Mocha и Handlebars.

Что такое Node.js

Node — это всего лишь среда или среда выполнения, в которой можно запускать обычный JavaScript (с небольшими отличиями) вне браузера. Мы можем использовать его для создания настольных приложений (с такими фреймворками, как Electron), написания веб-серверов или серверов приложений и многого другого.

Еще после прыжка! Продолжить чтение ниже ↓

Блокирующий/неблокирующий и синхронный/асинхронный

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

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

Давайте рассмотрим еще один пример синхронного вызова, который блокирует поток. Предположим, мы создаем приложение, которое сравнивает результаты двух API-интерфейсов Weather, чтобы найти их процентную разницу в температуре. Блокирующим образом делаем вызов Weather API One и ждем результата. Получив результат, мы вызываем Weather API Two и ждем его результата. На этом этапе не беспокойтесь, если вы не знакомы с API. Мы расскажем о них в следующем разделе. А пока представьте себе API как среду, через которую два компьютера могут взаимодействовать друг с другом.

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

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

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

В случае неблокирующего вызова у нас было бы что-то вроде этого:

График, демонстрирующий тот факт, что асинхронные неблокирующие операции выполняются почти на 50 процентов быстрее.
Динамика асинхронных неблокирующих операций во времени (большой предварительный просмотр)

Вы можете ясно видеть, насколько быстрее мы завершили выполнение. Вместо того, чтобы ждать API One, а затем ждать API Two, мы могли бы дождаться их одновременного завершения и получить наши результаты почти на 50 % быстрее. Обратите внимание: как только мы вызвали API One и начали ждать его ответа, мы также вызвали API Two и начали ждать его ответа одновременно с One.

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

Функции обратного вызова

Вы можете задаться вопросом: «Если мы можем обрабатывать вызов асинхронно, как мы узнаем, что этот вызов завершен и у нас есть ответ?» Как правило, мы передаем в качестве аргумента нашему асинхронному методу функцию обратного вызова, и этот метод «отзовет» эту функцию позже с ответом. Здесь я использую функции ES5, но позже мы обновим их до стандартов ES6.

 function asyncAddFunction(a, b, callback) { callback(a + b); //This callback is the one passed in to the function call below. } asyncAddFunction(2, 4, function(sum) { //Here we have the sum, 2 + 4 = 6. });

Такая функция называется «функцией высшего порядка», поскольку она принимает функцию (наш обратный вызов) в качестве аргумента. В качестве альтернативы функция обратного вызова может принимать объект ошибки и объект ответа в качестве аргументов и представлять их по завершении асинхронной функции. Мы увидим это позже с Express. Когда мы вызвали asyncAddFunction(...) , вы заметите, что мы предоставили функцию обратного вызова для параметра обратного вызова из определения метода. Эта функция является анонимной функцией (у нее нет имени) и написана с использованием синтаксиса выражения . Определение метода, с другой стороны, является оператором функции. Это не анонимно, потому что у него действительно есть имя (это «asyncAddFunction»).

Некоторые могут заметить путаницу, поскольку в определении метода мы указываем имя «обратный вызов». Однако анонимная функция, переданная в качестве третьего параметра asyncAddFunction(...) , не знает об имени и поэтому остается анонимной. Мы также не можем выполнить эту функцию позже по имени, нам придется снова пройти через функцию асинхронного вызова, чтобы запустить ее.

В качестве примера синхронного вызова мы можем использовать метод Node.js readFileSync(...) . Опять же, позже мы перейдем на ES6+.

 var fs = require('fs'); var data = fs.readFileSync('/example.txt'); // The thread will be blocked here until complete.

Если бы мы делали это асинхронно, мы бы передали функцию обратного вызова, которая сработает, когда асинхронная операция будет завершена.

 var fs = require('fs'); var data = fs.readFile('/example.txt', function(err, data) { //Move on, this will fire when ready. if(err) return console.log('Error: ', err); console.log('Data: ', data); // Assume var data is defined above. }); // Keep executing below, don't wait on the data.

Если вы никогда раньше не видели, чтобы return использовался таким образом, мы просто говорим остановить выполнение функции, чтобы мы не печатали объект данных, если объект ошибки определен. Мы также могли бы просто обернуть оператор журнала в предложение else .

Как и наша asyncAddFunction(...) , код функции fs.readFile(...) будет примерно таким:

 function readFile(path, callback) { // Behind the scenes code to read a file stream. // The data variable is defined up here. callback(undefined, data); //Or, callback(err, undefined); }

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

 setTimeout(function() { // ... }, 1000);

Метод setTimeout(...) принимает функцию обратного вызова для первого параметра, которая будет запущена через количество миллисекунд, указанное в качестве второго аргумента.

Рассмотрим более сложный пример:

 console.log('Initiated program.'); setTimeout(function() { console.log('3000 ms (3 sec) have passed.'); }, 3000); setTimeout(function() { console.log('0 ms (0 sec) have passed.'); }, 0); setTimeout(function() { console.log('1000 ms (1 sec) has passed.'); }, 1000); console.log('Terminated program');

На выходе мы получаем:

 Initiated program. Terminated program. 0 ms (0 sec) have passed. 1000 ms (1 sec) has passed. 3000 ms (3 sec) have passed.

Вы можете видеть, что первый оператор журнала работает, как и ожидалось. Мгновенно последний оператор журнала выводится на экран, так как это происходит до того, как 0 секунд превзойдется после второго setTimeout(...) . Сразу же после этого выполняются второй, третий и первый методы setTimeout(...) .

Если бы Node.js не был неблокирующим, мы бы увидели первый оператор журнала, подождали бы 3 секунды, чтобы увидеть следующий, мгновенно увидели бы третий (0-секундный setTimeout(...) , а затем должны были бы ждать еще один секунду, чтобы увидеть последние два оператора журнала. Неблокирующий характер Node заставляет все таймеры начинать обратный отсчет с момента выполнения программы, а не в порядке, в котором они набираются. Вы можете изучить API-интерфейсы Node, Callstack и цикл событий для получения дополнительной информации о том, как Node работает внутри.

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

Обещания вместо обратных вызовов

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

В качестве примера предположим, что мы делаем вызов API, и наша функция с не столь уникальным названием ' makeAPICall(...) ' принимает URL-адрес и обратный вызов.

Наша функция makeAPICall(...) будет определена как

 function makeAPICall(path, callback) { // Attempt to make API call to path argument. // ... callback(undefined, res); // Or, callback(err, undefined); depending upon the API's response. }

и мы бы назвали это с помощью:

 makeAPICall('/example', function(err1, res1) { if(err1) return console.log('Error: ', err1); // ... });

Если бы мы хотели сделать еще один вызов API, используя ответ от первого, нам пришлось бы вложить оба обратных вызова. Предположим, мне нужно внедрить свойство userName из объекта res1 в путь второго вызова API. Мы бы хотели иметь:

 makeAPICall('/example', function(err1, res1) { if(err1) return console.log('Error: ', err1); makeAPICall('/newExample/' + res1.userName, function(err2, res2) { if(err2) return console.log('Error: ', err2); console.log(res2); }); });

Примечание . Метод ES6+ для внедрения свойства res1.userName вместо конкатенации строк заключается в использовании «Строки шаблона». Таким образом, вместо того, чтобы заключать нашу строку в кавычки ( ' или " ), мы будем использовать обратные кавычки ( ` ). Расположенные под клавишей Escape на вашей клавиатуре. Затем мы будем использовать нотацию ${} для встраивания любого выражения JS внутри В конце концов, наш прежний путь будет выглядеть так: /newExample/${res.UserName} , заключенный в обратные кавычки.

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

 makeAPICall('/example').then(function(res) { // Success callback. // ... }, function(err) { // Failure callback. console.log('Error:', err); });

Первый аргумент функции then() — это обратный вызов при успешном выполнении, а второй аргумент — обратный вызов при отказе. В качестве альтернативы мы могли бы потерять второй аргумент для .then() и вместо этого вызвать .catch() . Аргументы для .then() необязательны, и вызов .catch() будет эквивалентен .then(successCallback, null) .

Используя .catch() , мы имеем:

 makeAPICall('/example').then(function(res) { // Success callback. // ... }).catch(function(err) { // Failure Callback console.log('Error: ', err); });

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

 makeAPICall('/example') .then(function(res) { // ... }) .catch(function(err) { console.log('Error: ', err); });

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

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

Конструктор Promise принимает одну функцию обратного вызова в качестве аргумента, которая получает два параметра — resolve и reject , которые мы будем вызывать позже, чтобы запустить либо успешный обратный вызов в .then() , либо .then() сбой. обратный вызов или .catch() , если он предоставлен.

Вот пример того, как это выглядит:

 var examplePromise = new Promise(function(resolve, reject) { // Do whatever we are going to do and then make the appropiate call below: resolve('Happy!'); // — Everything worked. reject('Sad!'); // — We noticed that something went wrong. }):

Затем мы можем использовать:

 examplePromise.then(/* Both callback functions in here */); // Or, the success callback in .then() and the failure callback in .catch().

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

 function makeAPICall(path) { return new Promise(function(resolve, reject) { // Make our async API call here. if (/* All is good */) return resolve(res); //res is the response, would be defined above. else return reject(err); //err is error, would be defined above. }); }

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

Рефакторинг нашего URL-адреса с несколькими API выше с обещаниями, мы получаем:

 makeAPICall('/example').then(function(res) { // First response callback. Fires on success to '/example' call. return makeAPICall(`/newExample/${res.UserName}`); // Returning new call allows for Promise Chaining. }, function(err) { // First failure callback. Fires if there is a failure calling with '/example'. console.log('Error:', err); }).then(function(res) { // Second response callback. Fires on success to returned '/newExample/...' call. console.log(res); }, function(err) { // Second failure callback. Fire if there is a failure calling with '/newExample/...' console.log('Error:', err); });

Обратите внимание, что сначала мы вызываем makeAPICall('/example') . Это возвращает обещание, поэтому мы присоединяем .then() . Внутри then() мы возвращаем новый вызов makeAPICall(...) , который сам по себе, как было показано ранее, возвращает промис, позволяя нам создать цепочку для нового .then() после первого.

Как и выше, мы можем реструктурировать это для удобочитаемости и удалить обратные вызовы с ошибкой для универсального предложения catch() all. Затем мы можем следовать принципу DRY (не повторяйтесь) и реализовать обработку ошибок только один раз.

 makeAPICall('/example') .then(function(res) { // Like earlier, fires with success and response from '/example'. return makeAPICall(`/newExample/${res.UserName}`); // Returning here lets us chain on a new .then(). }) .then(function(res) { // Like earlier, fires with success and response from '/newExample'. console.log(res); }) .catch(function(err) { // Generic catch all method. Fires if there is an err with either earlier call. console.log('Error: ', err); });

Обратите внимание, что обратные вызовы успеха и неудачи в .then() срабатывают только для статуса отдельного промиса, которому соответствует .then() . Блок catch , однако, будет перехватывать любые ошибки, возникающие в любом из .then() s.

ES6 Const против Let

Во всех наших примерах мы использовали функции ES5 и старое ключевое слово var . Несмотря на то, что миллионы строк кода все еще выполняются сегодня с использованием этих методов ES5, полезно обновиться до текущих стандартов ES6+, и мы проведем рефакторинг части нашего кода выше. Начнем с const и let .

Возможно, вы привыкли объявлять переменную с помощью ключевого слова var :

 var pi = 3.14;

Со стандартами ES6+ мы могли бы сделать это либо

 let pi = 3.14;

или

 const pi = 3.14;

где const означает «константа» — значение, которое нельзя переназначить позже. (За исключением свойств объекта — мы рассмотрим это в ближайшее время. Кроме того, переменные, объявленные const , не являются неизменяемыми, только ссылка на переменную.)

В старом JavaScript блочные области, например, в if , while , {} . for и т. д. никак не повлияли на var , и это сильно отличается от более статически типизированных языков, таких как Java или C++. То есть область действия var — это вся объемлющая функция, и она может быть глобальной (если находится вне функции) или локальной (если находится внутри функции). Чтобы продемонстрировать это, см. следующий пример:

 function myFunction() { var num = 5; console.log(num); // 5 console.log('--'); for(var i = 0; i < 10; i++) { var num = i; console.log(num); //num becomes 0 — 9 } console.log('--'); console.log(num); // 9 console.log(i); // 10 } myFunction();

Выход:

 5 --- 0 1 2 3 ... 7 8 9 --- 9 10

Здесь важно отметить, что определение нового var num внутри области for напрямую повлияло на var num вне и над for . Это связано с тем, что областью действия var всегда является объемлющая функция, а не блок.

Опять же, по умолчанию var i внутри for() по умолчанию соответствует области действия myFunction , поэтому мы можем получить доступ к i вне цикла и получить 10.

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

 function myFunction() { let num = 5; console.log(num); // 5 for(let i = 0; i < 10; i++) { let num = i; console.log('--'); console.log(num); // num becomes 0 — 9 } console.log('--'); console.log(num); // 5 console.log(i); // undefined, ReferenceError }

Глядя на ключевое слово const , вы можете видеть, что мы получаем ошибку, если пытаемся переназначить его:

 const c = 299792458; // Fact: The constant "c" is the speed of light in a vacuum in meters per second. c = 10; // TypeError: Assignment to constant variable.

Все становится интереснее, когда мы присваиваем const переменную объекту:

 const myObject = { name: 'Jane Doe' }; // This is illegal: TypeError: Assignment to constant variable. myObject = { name: 'John Doe' }; // This is legal. console.log(myObject.name) -> John Doe myObject.name = 'John Doe';

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

Стрелочные функции ES6

Возможно, вы использовали для создания такой функции:

 function printHelloWorld() { console.log('Hello, World!'); }

С функциями стрелок это будет:

 const printHelloWorld = () => { console.log('Hello, World!'); };

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

 const squareNumber = (x) => { return x * x; } squareNumber(5); // We can call an arrow function like an ES5 functions. Returns 25.

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

Важно отметить, что несмотря на то, что круглые скобки необходимы, если наша функция не принимает аргументов (как в случае с printHelloWorld() выше), мы можем опустить круглые скобки, если она принимает только один, поэтому наше предыдущее определение метода squareNumber() можно переписать как:

 const squareNumber = x => { // Notice we have dropped the parentheses for we only take in one argument. return x * x; }

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

Наконец, если мы хотим неявно вернуть только одно выражение, как в случае с squareNumber(...) выше, мы можем поместить оператор return в соответствие с сигнатурой метода:

 const squareNumber = x => x * x;

То есть,

 const test = (a, b, c) => expression

такой же как

 const test = (a, b, c) => { return expression }

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

 const test = () => ({ pi: 3.14 }); // Spaces between brackets are a formality to make the code look cleaner.

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

asyncAddFunction(...) , приведенный выше, может быть реорганизован из:

 function asyncAddFunction(a, b, callback){ callback(a + b); }

к:

 const aysncAddFunction = (a, b, callback) => { callback(a + b); };

или даже к:

 const aysncAddFunction = (a, b, callback) => callback(a + b); // This will return callback(a + b).

При вызове функции мы могли бы передать функцию со стрелкой для обратного вызова:

 asyncAddFunction(10, 12, sum => { // No parentheses because we only take one argument. console.log(sum); }

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

 makeAPICall('/example') .then(res => makeAPICall(`/newExample/${res.UserName}`)) .then(res => console.log(res)) .catch(err => console.log('Error: ', err));

Теперь есть некоторые предостережения относительно функций стрелок. Во-первых, они не связывают ключевое слово this . Предположим, у меня есть следующий объект:

 const Person = { name: 'John Doe', greeting: () => { console.log(`Hi. My name is ${this.name}.`); } }

Вы можете ожидать, что вызов Person.greeting() вернет «Привет. Меня зовут Джон Доу». Вместо этого мы получаем: «Привет. Мое имя не определено». Это связано с тем, что стрелочные функции не имеют this , и поэтому при попытке использовать this внутри стрелочной функции по умолчанию используется this объемлющей области, а объемлющей областью объекта Person является window в браузере или module.exports в Узел.

Чтобы доказать это, если мы снова воспользуемся тем же объектом, но установим свойство name глобального this на что-то вроде «Jane Doe», тогда this.name в стрелочной функции вернет «Jane Doe», потому что глобальное this находится внутри охватывающую область или является родителем объекта Person .

 this.name = 'Jane Doe'; const Person = { name: 'John Doe', greeting: () => { console.log(`Hi. My name is ${this.name}.`); } } Person.greeting(); // Hi. My name is Jane Doe

Это известно как «лексическая область видимости», и мы можем обойти это, используя так называемый «краткий синтаксис», в котором мы теряем двоеточие и стрелку, чтобы реорганизовать наш объект как таковой:

 const Person = { name: 'John Doe', greeting() { console.log(`Hi. My name is ${this.name}.`); } } Person.greeting() //Hi. My name is John Doe.

Классы ES6

Хотя JavaScript никогда не поддерживал классы, вы всегда могли эмулировать их с помощью таких объектов, как показано выше. EcmaScript 6 обеспечивает поддержку классов с использованием ключевых слов class и new :

 class Person { constructor(name) { this.name = name; } greeting() { console.log(`Hi. My name is ${this.name}.`); } } const person = new Person('John'); person.greeting(); // Hi. My name is John.

Функция конструктора вызывается автоматически при использовании new ключевого слова, в которое мы можем передавать аргументы для первоначальной настройки объекта. Это должно быть знакомо любому читателю, имеющему опыт работы с более статически типизированными объектно-ориентированными языками программирования, такими как Java, C++ и C#.

Не вдаваясь в подробности концепций ООП, еще одной такой парадигмой является «наследование», которое позволяет одному классу наследоваться от другого. Например, класс с именем Car будет очень общим — он будет содержать такие методы, как «стоп», «старт» и т. д., которые нужны всем автомобилям. Таким образом, подмножество класса SportsCar может наследовать основные операции от Car и переопределять все, что ему нужно. Мы могли бы обозначить такой класс следующим образом:

 class Car { constructor(licensePlateNumber) { this.licensePlateNumber = licensePlateNumber; } start() {} stop() {} getLicensePlate() { return this.licensePlateNumber; } // … } class SportsCar extends Car { constructor(engineRevCount, licensePlateNumber) { super(licensePlateNumber); // Pass licensePlateNumber up to the parent class. this.engineRevCount = engineRevCount; } start() { super.start(); } stop() { super.stop(); } getLicensePlate() { return super.getLicensePlate(); } getEngineRevCount() { return this.engineRevCount; } }

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

События JavaScript

Событие — это действие, на которое вы можете отреагировать. Предположим, вы создаете форму входа для своего приложения. Когда пользователь нажимает кнопку «Отправить», вы можете отреагировать на это событие с помощью «обработчика событий» в вашем коде — обычно это функция. Когда эта функция определена как обработчик события, мы говорим, что «регистрируем обработчик события». Обработчик событий для нажатия кнопки отправки, скорее всего, проверит форматирование ввода, предоставленного пользователем, и очистит его, чтобы предотвратить такие атаки, как SQL-инъекции или межсайтовые сценарии (помните, что никакой код на стороне клиента никогда не может быть рассмотрен). всегда очищайте данные на сервере — никогда не доверяйте ничему из браузера), а затем проверьте, не выходит ли эта комбинация имени пользователя и пароля из базы данных, чтобы аутентифицировать пользователя и предоставить ему токен.

Поскольку это статья о Node, мы сосредоточимся на модели событий Node.

Мы можем использовать модуль events из Node, чтобы генерировать и реагировать на определенные события. Любой объект, генерирующий событие, является экземпляром класса EventEmitter .

Мы можем сгенерировать событие, вызвав метод emit() , и прослушиваем это событие с помощью метода on() , оба из которых доступны через класс EventEmitter .

 const EventEmitter = require('events'); const myEmitter = new EventEmitter();

Теперь, myEmitter является экземпляром класса EventEmitter , мы можем получить доступ к emit() и on() :

 const EventEmitter = require('events'); const myEmitter = new EventEmitter(); myEmitter.on('someEvent', () => { console.log('The "someEvent" event was fired (emitted)'); }); myEmitter.emit('someEvent'); // This will call the callback function above.

Второй параметр myEmitter.on() — это функция обратного вызова, которая срабатывает при отправке события — это обработчик события. Первый параметр — это имя события, которое может быть любым, которое нам нравится, хотя рекомендуется соглашение об именах в стиле camelCase.

Кроме того, обработчик события может принимать любое количество аргументов, которые передаются при генерации события:

 const EventEmitter = require('events'); const myEmitter = new EventEmitter(); myEmitter.on('someEvent', (data) => { console.log(`The "someEvent" event was fired (emitted) with data: ${data}`); }); myEmitter.emit('someEvent', 'This is the data payload');

Используя наследование, мы можем предоставить методы emit emit() и on() из EventEmitter любому классу. Это делается путем создания класса Node.js и использования зарезервированного ключевого слова extends для наследования свойств, доступных в EventEmitter :

 const EventEmitter = require('events'); class MyEmitter extends EventEmitter { // This is my class. I can emit events from a MyEmitter object. }

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

 const EventEmitter = require('events'); class Vehicle extends EventEmitter { collide(collisionStatistics) { this.emit('collision', collisionStatistics) } } const myVehicle = new Vehicle(); myVehicle.on('collision', collisionStatistics => { console.log('WARNING! Vehicle Impact Detected: ', collisionStatistics); notifyVendor(collisionStatistics); }); myVehicle.collide({ ... });

Это запутанный пример, потому что мы могли бы просто поместить код в обработчике событий внутри функции столкновения класса, но тем не менее он демонстрирует, как работает модель событий узла. Обратите внимание, что в некоторых учебниках будет показан метод util.inherits() , позволяющий объекту генерировать события. Это устарело в пользу классов и расширений extends .

Менеджер пакетов узла

При программировании с использованием Node и JavaScript довольно часто можно услышать о npm . Npm — это менеджер пакетов, который делает именно это — разрешает загрузку сторонних пакетов, которые решают распространенные проблемы в JavaScript. Другие решения, такие как Yarn, Npx, Grunt и Bower, также существуют, но в этом разделе мы сосредоточимся только на npm и на том, как вы можете установить зависимости для своего приложения через простой интерфейс командной строки (CLI), используя его.

Начнем с простого, всего с npm . Посетите домашнюю страницу NpmJS, чтобы просмотреть все пакеты, доступные в NPM. Когда вы начинаете новый проект, который будет зависеть от пакетов NPM, вам нужно будет запустить npm init через терминал в корневом каталоге вашего проекта. Вам будет задан ряд вопросов, которые будут использованы для создания файла package.json . В этом файле хранятся все ваши зависимости — модули, от которых зависит ваше приложение, скрипты — предопределенные команды терминала для запуска тестов, сборки проекта, запуска сервера разработки и т. д. и т. д. и многое другое.

Чтобы установить пакет, просто запустите npm install [package-name] --save . Флаг save гарантирует, что пакет и его версия будут зарегистрированы в файле package.json . Начиная с версии 5 npm , зависимости сохраняются по умолчанию, поэтому --save можно не указывать. Вы также заметите новую папку node_modules , содержащую код только что установленного пакета. Его также можно сократить до npm i [package-name] . В качестве полезного примечания: папку node_modules никогда не следует включать в репозиторий GitHub из-за ее размера. Всякий раз, когда вы клонируете репозиторий из GitHub (или любой другой системы управления версиями), обязательно запустите команду npm install , чтобы выйти и получить все пакеты, определенные в файле package.json , автоматически создавая каталог node_modules . Вы также можете установить пакет определенной версии: например, npm i [package-name]@1.10.1 --save .

Удаление пакета аналогично его установке: npm remove [package-name] .

Вы также можете установить пакет глобально. Этот пакет будет доступен во всех проектах, а не только в том, над которым вы работаете. Вы делаете это с флагом -g после npm i [package-name] . Это обычно используется для интерфейсов командной строки, таких как Google Firebase и Heroku. Несмотря на простоту этого метода, глобальная установка пакетов обычно считается плохой практикой, поскольку они не сохраняются в файле package.json , и если другой разработчик попытается использовать ваш проект, он не получит все необходимые зависимости от npm install .

API и JSON

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

API — это интерфейс прикладного программирования , и в основном это метод, с помощью которого две несвязанные системы могут взаимодействовать друг с другом. Говоря более технически, API позволяет системе или компьютерной программе (обычно серверу) получать запросы и отправлять соответствующие ответы (клиенту, также известному как хост).

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

As a developer, you want to focus on building your app and monetizing it, not putting the infrastructure in place to geocode addresses or placing weather stations in every city.

Luckily for you, companies like Google and OpenWeatherMap have already put that infrastructure in place, you just need a way to talk to it — that is where the API comes in. While, as of now, we have developed a very abstract and ambiguous definition of the API, bear with me. We'll be getting to tangible examples soon.

Now, it costs money for companies to develop, maintain, and secure that aforementioned infrastructure, and so it is common for corporations to sell you access to their API. This is done with that is known as an API key, a unique alphanumeric identifier associating you, the developer, with the API. Every time you ask the API to send you data, you pass along your API key. The server can then authenticate you and keep track of how many API calls you are making, and you will be charged appropriately. The API key also permits Rate-Limiting or API Call Throttling (a method of throttling the number of API calls in a certain timeframe as to not overwhelm the server, preventing DOS attacks — Denial of Service). Most companies, however, will provide a free quota, giving you, as an example, 25,000 free API calls a day before charging you.

Up to this point, we have established that an API is a method by which two computer programs can communicate with each other. If a server is storing data, such as a website, and your browser makes a request to download the code for that site, that was the API in action.

Let us look at a more tangible example, and then we'll look at a more real-world, technical one. Suppose you are eating out at a restaurant for dinner. You are equivalent to the client, sitting at the table, and the chef in the back is equivalent to the server.

Since you will never directly talk to the chef, there is no way for him/her to receive your request (for what order you would like to make) or for him/her to provide you with your meal once you order it. We need someone in the middle. In this case, it's the waiter, analogous to the API. The API provides a medium with which you (the client) may talk to the server (the chef), as well as a set of rules for how that communication should be made (the menu — one meal is allowed two sides, etc.)

Now, how do you actually talk to the API (the waiter)? You might speak English, but the chef might speak Spanish. Is the waiter expected to know both languages to translate? What if a third person comes in who only speaks Mandarin? Что тогда? Well, all clients and servers have to agree to speak a common language, and in computer programming, that language is JSON, pronounced JAY-sun, and it stands for JavaScript Object Notation.

At this point, we don't quite know what JSON looks like. It's not a computer programming language, it's just, well, a language, like English or Spanish, that everyone (everyone being computers) understands on a guaranteed basis. It's guaranteed because it's a standard, notably RFC 8259 , the JavaScript Object Notation (JSON) Data Interchange Format by the Internet Engineering Task Force (IETF).

Even without formal knowledge of what JSON actually is and what it looks like (we'll see in an upcoming article in this series), we can go ahead introduce a technical example operating on the Internet today that employs APIs and JSON. APIs and JSON are not just something you can choose to use, it's not equivalent to one out of a thousand JavaScript frameworks you can pick to do the same thing. It is THE standard for data exchange on the web.

Suppose you are building a travel website that compares prices for aircraft, rental car, and hotel ticket prices. Let us walk through, step-by-step, on a high level, how we would build such an application. Of course, we need our User Interface, the front-end, but that is out of scope for this article.

We want to provide our users with the lowest price booking method. Well, that means we need to somehow attain all possible booking prices, and then compare all of the elements in that set (perhaps we store them in an array) to find the smallest element (known as the infimum in mathematics.)

How will we get this data? Well, suppose all of the booking sites have a database full of prices. Those sites will provide an API, which exposes the data in those databases for use by you. You will call each API for each site to attain all possible booking prices, store them in your own array, find the lowest or minimum element of that array, and then provide the price and booking link to your user. We'll ask the API to query its database for the price in JSON, and it will respond with said price in JSON to us. We can then use, or parse, that accordingly. We have to parse it because APIs will return JSON as a string, not the actual JavaScript data type of JSON. This might not make sense now, and that's okay. We'll be covering it more in a future article.

Also, note that just because something is called an API does not necessarily mean it operates on the web and sends and receives JSON. The Java API, for example, is just the list of classes, packages, and interfaces that are part of the Java Development Kit (JDK), providing programming functionality to the programmer.

Хорошо. We know we can talk to a program running on a server by way of an Application Programming Interface, and we know that the common language with which we do this is known as JSON. But in the web development and networking world, everything has a protocol. What do we actually do to make an API call, and what does that look like code-wise? That's where HTTP Requests enter the picture, the HyperText Transfer Protocol, defining how messages are formatted and transmitted across the Internet. Once we have an understanding of HTTP (and HTTP verbs, you'll see that in the next section), we can look into actual JavaScript frameworks and methods (like fetch() ) offered by the JavaScript API (similar to the Java API), that actually allow us to make API calls.

HTTP And HTTP Requests

HTTP is the HyperText Transfer Protocol. It is the underlying protocol that determines how messages are formatted as they are transmitted and received across the web. Let's think about what happens when, for example, you attempt to load the home page of Smashing Magazine in your web browser.

You type the website URL (Uniform Resource Locator) in the URL bar, where the DNS server (Domain Name Server, out of scope for this article) resolves the URL into the appropriate IP Address. The browser makes a request, called a GET Request, to the Web Server to, well, GET the underlying HTML behind the site. The Web Server will respond with a message such as “OK”, and then will go ahead and send the HTML down to the browser where it will be parsed and rendered accordingly.

There are a few things to note here. First, the GET Request, and then the “OK” response. Suppose you have a specific database, and you want to write an API to expose that database to your users. Suppose the database contains books the user wants to read (as it will in a future article in this series). Then there are four fundamental operations your user may want to perform on this database, that is, Create a record, Read a record, Update a record, or Delete a record, known collectively as CRUD operations.

Let's look at the Read operation for a moment. Without incorrectly assimilating or conflating the notion of a web server and a database, that Read operation is very similar to your web browser attempting to get the site from the server, just as to read a record is to get the record from the database.

Это известно как HTTP-запрос. Вы отправляете запрос на какой-то сервер где-то, чтобы получить какие-то данные, и поэтому запрос называется «GET», причем заглавные буквы являются стандартным способом обозначения таких запросов.

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

Обновление CRUD позволяет нам использовать запрос PUT или PATCH для обновления ресурса. PUT HTTP либо создаст новую запись, либо обновит/заменит старую.

Давайте рассмотрим это немного подробнее, а затем перейдем к PATCH.

API обычно работает, отправляя HTTP-запросы к определенным маршрутам в URL-адресе. Предположим, мы создаем API для взаимодействия с БД, содержащей список книг пользователя. Тогда мы сможем просматривать эти книги по URL-адресу .../books . Запросы POST к .../books создадут новую книгу с любыми свойствами, которые вы определите (например, идентификатор, название, ISBN, автор, данные публикации и т. д.) на маршруте .../books . Неважно, какая базовая структура данных сейчас хранит все книги в .../books . Мы просто заботимся о том, чтобы API открывал эту конечную точку (доступную через маршрут) для манипулирования данными. Предыдущее предложение было ключевым: запрос POST создает новую книгу по маршруту ...books/ . Таким образом, разница между PUT и POST заключается в том, что PUT создаст новую книгу (как и в случае POST), если такой книги не существует, или заменит существующую книгу , если книга уже существует в этой вышеупомянутой структуре данных.

Предположим, что у каждой книги есть следующие свойства: id, title, ISBN, author, hasRead (логическое значение).

Затем, чтобы добавить новую книгу, как показано ранее, мы должны сделать POST-запрос к .../books . Если бы мы хотели полностью обновить или заменить книгу, мы бы сделали запрос PUT к .../books/id , где id — это идентификатор книги, которую мы хотим заменить.

В то время как PUT полностью заменяет существующую книгу, PATCH обновляет что-то, имеющее отношение к конкретной книге, возможно, изменяя логическое свойство hasRead , которое мы определили выше, поэтому мы делаем запрос PATCH к …/books/id , отправляя новые данные.

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

Есть еще одна фундаментальная операция CRUD, которая называется Delete. Как и следовало ожидать, имя такого HTTP-запроса — «DELETE», и он работает почти так же, как PATCH, требуя, чтобы идентификатор книги был указан в маршруте.

Таким образом, мы узнали, что маршруты — это конкретные URL-адреса, на которые вы делаете HTTP-запрос, а конечные точки — это функции, предоставляемые API, которые что-то делают с данными, которые он предоставляет. То есть конечная точка — это функция языка программирования, расположенная на другом конце маршрута, и она выполняет любой указанный вами HTTP-запрос. Мы также узнали, что существуют такие термины, как POST, GET, PUT, PATCH, DELETE и другие (известные как HTTP-глаголы), которые фактически определяют, какие запросы вы отправляете к API. Как и JSON, эти методы HTTP-запросов являются интернет-стандартами, определенными Инженерной группой Интернета (IETF), в первую очередь RFC 7231, раздел четвертый: методы запроса, и RFC 5789, раздел второй: метод исправления, где RFC является аббревиатурой от Запрос комментариев.

Итак, мы можем сделать запрос GET к URL-адресу .../books/id , где переданный идентификатор известен как параметр. Мы могли бы сделать запрос POST, PUT или PATCH к .../books для создания ресурса или к .../books/id для изменения/замены/обновления ресурса. И мы также можем сделать запрос DELETE к .../books/id , чтобы удалить конкретную книгу.

Полный список методов HTTP-запросов можно найти здесь.

Также важно отметить, что после выполнения HTTP-запроса мы получим ответ. Конкретный ответ определяется тем, как мы строим API, но вы всегда должны получать код состояния. Ранее мы говорили, что когда ваш веб-браузер запрашивает HTML-код с веб-сервера, он отвечает «ОК». Это известно как код состояния HTTP, точнее, HTTP 200 OK. Код состояния просто указывает, как завершилась операция или действие, указанное в конечной точке (помните, что это наша функция, которая выполняет всю работу). Коды состояния HTTP отправляются обратно сервером, и, вероятно, многие из них вам знакомы, например, 404 Not Found (не удалось найти ресурс или файл, это похоже на запрос GET к .../books/id , если такого идентификатора не существует.)

Полный список кодов состояния HTTP можно найти здесь.

MongoDB

MongoDB — это нереляционная база данных NoSQL, похожая на базу данных реального времени Firebase. Вы будете общаться с базой данных через пакет Node, такой как собственный драйвер MongoDB или Mongoose.

В MongoDB данные хранятся в формате JSON, который сильно отличается от реляционных баз данных, таких как MySQL, PostgreSQL или SQLite. Оба называются базами данных, при этом таблицы SQL называются коллекциями, строки таблицы SQL называются документами, а столбцы таблицы SQL называются полями.

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

Рекомендуется прочитать документы MongoDB, чтобы узнать, как создать активную базу данных в кластере Atlas и выполнять с ней операции CRUD с помощью встроенного драйвера MongoDB. В следующей статье этой серии мы узнаем, как настроить локальную базу данных и рабочую базу данных в облаке.

Создание приложения узла командной строки

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

Давайте четко понимать, что мы строим. Нас не будет интересовать пользовательский ввод, поэтому мы не будем использовать такие пакеты, как Yargs. Мы также не будем создавать собственный API. Это будет в следующей статье этой серии, когда мы будем использовать Express Web Application Framework. Я использую этот подход, чтобы не смешивать Node.js с мощью Express и API, как это делают в большинстве руководств. Вместо этого я предоставлю один метод (из многих), с помощью которого можно вызывать и получать данные из внешнего API, использующего стороннюю библиотеку JavaScript. API, который мы будем вызывать, — это Weather API, к которому мы будем обращаться из Node и выводить его выходные данные на терминал, возможно, с некоторым форматированием, известным как «красивая печать». Я расскажу обо всем процессе, в том числе о том, как настроить API и получить ключ API, шаги которого дают правильные результаты по состоянию на январь 2019 года.

Мы будем использовать API OpenWeatherMap для этого проекта, поэтому для начала перейдите на страницу регистрации OpenWeatherMap и создайте учетную запись с помощью формы. После входа в систему найдите пункт меню «Ключи API» на странице панели управления (находится здесь). Если вы только что создали учетную запись, вам нужно будет выбрать имя для своего ключа API и нажать «Создать». Чтобы ваш новый API-ключ заработал и был связан с вашей учетной записью, может пройти не менее 2 часов.

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

Из документов мы видим, что метод, с помощью которого мы это делаем, заключается в предоставлении следующего URL-адреса:

 api.openweathermap.org/data/2.5/weather?zip={zip code},{country code}

В который мы могли бы ввести данные:

 api.openweathermap.org/data/2.5/weather?zip=94040,us

Теперь, прежде чем мы действительно сможем получить соответствующие данные из этого API, нам нужно предоставить наш новый ключ API в качестве параметра запроса:

 api.openweathermap.org/data/2.5/weather?zip=94040,us&appid={YOUR_API_KEY}

А пока скопируйте этот URL-адрес на новую вкладку в веб-браузере, заменив заполнитель {YOUR_API_KEY} ключом API, который вы получили ранее при регистрации учетной записи.

Текст, который вы видите, на самом деле является JSON — согласованным языком Интернета, как обсуждалось ранее.

Чтобы проверить это дальше, нажмите Ctrl + Shift + I в Google Chrome, чтобы открыть инструменты разработчика Chrome, а затем перейдите на вкладку «Сеть». В настоящее время здесь не должно быть никаких данных.

Пустая вкладка «Сеть» в Chrome Dev Tools
Пустые инструменты разработчика Google Chrome (большой предварительный просмотр)

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

Заполненная вкладка «Сеть» в Chrome Dev Tools
Заполненные инструменты разработчика Google Chrome (большой предварительный просмотр)

После того, как вы нажмете на эту ссылку, мы сможем просмотреть информацию, относящуюся к HTTP, например заголовки. Заголовки отправляются в ответе от API (в некоторых случаях вы также можете отправить свои собственные заголовки в API или даже создать свои собственные заголовки (часто с префиксом x- ) для отправки обратно при создании собственного API. ) и просто содержат дополнительную информацию, которая может понадобиться клиенту или серверу.

В этом случае вы можете видеть, что мы сделали запрос HTTP GET к API, и он ответил со статусом HTTP 200 OK. Вы также можете видеть, что данные, отправленные обратно, были в формате JSON, как указано в разделе «Заголовки ответа».

Инструменты Google Dev, изображающие заголовки из ответа API
Заголовки в ответе от API (Большой предварительный просмотр)

Если вы перейдете на вкладку предварительного просмотра, вы сможете просмотреть JSON как объект JavaScript. Текстовая версия, которую вы видите в своем браузере, представляет собой строку, поскольку JSON всегда передается и принимается через Интернет в виде строки. Вот почему мы должны анализировать JSON в нашем коде, чтобы преобразовать его в более читаемый формат — в этом случае (и почти во всех случаях) — объект JavaScript.

Вы также можете использовать расширение Google Chrome «JSON View», чтобы сделать это автоматически.

Чтобы начать сборку нашего приложения, я открою терминал и создам новый корневой каталог, а затем cd в него. Оказавшись внутри, я создам новый файл app.js , запущу npm init для создания файла package.json с настройками по умолчанию, а затем открою Visual Studio Code.

 mkdir command-line-weather-app && cd command-line-weather-app touch app.js npm init code .

После этого я загружу Axios, проверю, добавлен ли он в мой файл package.json , и отмечу, что папка node_modules успешно создана.

В браузере вы можете видеть, что мы сделали запрос GET вручную, вручную введя правильный URL-адрес в строку URL-адреса. Axios — это то, что позволит мне сделать это внутри Node.

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

Первое, что я сделаю, это потребую пакет Axios, который мы установили ранее с помощью

 const axios = require('axios');

Теперь у нас есть доступ к Axios и мы можем делать соответствующие HTTP-запросы через константу axios .

Как правило, наши вызовы API будут динамическими — в этом случае мы можем захотеть ввести разные почтовые индексы и коды стран в наш URL-адрес. Итак, я буду создавать постоянные переменные для каждой части URL-адреса, а затем объединять их со строками шаблона ES6. Во-первых, у нас есть часть нашего URL, которая никогда не изменится, а также наш ключ API:

 const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here';

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

 const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us';

Теперь нам нужно объединить эти переменные в один URL-адрес, к которому мы можем использовать Axios для отправки запросов GET:

 const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`;

Вот содержимое нашего файла app.js до этого момента:

 const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`;

Все, что осталось сделать, это фактически использовать axios для отправки GET-запроса к этому URL-адресу. Для этого мы будем использовать метод get(url) , предоставляемый axios .

 axios.get(ENTIRE_API_URL)

axios.get(...) на самом деле возвращает обещание, и функция обратного вызова успеха примет аргумент ответа, который позволит нам получить доступ к ответу от API — то же самое, что вы видели в браузере. Я также добавлю предложение .catch() для обнаружения любых ошибок.

 axios.get(ENTIRE_API_URL) .then(response => console.log(response)) .catch(error => console.log('Error', error));

Если мы теперь запустим этот код с node app.js в терминале, вы сможете увидеть полный ответ, который мы получим. Однако предположим, что вы просто хотите увидеть температуру для этого почтового индекса — тогда большая часть этих данных в ответе вам бесполезна. Axios фактически возвращает ответ от API в объекте данных, который является свойством ответа. Это означает, что ответ от сервера на самом деле находится в response.data , поэтому давайте напечатаем его вместо этого в функции обратного вызова: console.log(response.data) .

Итак, мы сказали, что веб-серверы всегда обрабатывают JSON как строку, и это правда. Однако вы можете заметить, что response.data уже является объектом (очевидно, запустив console.log(typeof response.data) ) — нам не нужно было анализировать его с помощью JSON.parse() . Это потому, что Axios уже позаботится об этом за нас за кулисами.

Вывод в терминале от запуска console.log(response.data) можно отформатировать — «красиво напечатать» — запустив console.log(JSON.stringify(response.data, undefined, 2)) . JSON.stringify() преобразует объект JSON в строку и принимает объект, фильтр и количество символов для отступа при печати. Вы можете увидеть ответ, который это дает:

 { "coord": { "lon": -118.24, "lat": 33.97 }, "weather": [ { "id": 800, "main": "Clear", "description": "clear sky", "icon": "01d" } ], "base": "stations", "main": { "temp": 288.21, "pressure": 1022, "humidity": 15, "temp_min": 286.15, "temp_max": 289.75 }, "visibility": 16093, "wind": { "speed": 2.1, "deg": 110 }, "clouds": { "all": 1 }, "dt": 1546459080, "sys": { "type": 1, "id": 4361, "message": 0.0072, "country": "US", "sunrise": 1546441120, "sunset": 1546476978 }, "id": 420003677, "name": "Lynwood", "cod": 200 }

Теперь ясно видно, что искомая температура находится в main свойстве объекта response.data , поэтому мы можем получить к ней доступ, вызвав response.data.main.temp . Давайте посмотрим на код нашего приложения до сих пор:

 const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`; axios.get(ENTIRE_API_URL) .then(response => console.log(response.data.main.temp)) .catch(error => console.log('Error', error));

Температура, которую мы получаем, на самом деле измеряется в градусах Кельвина, что является температурной шкалой, обычно используемой в физике, химии и термодинамике из-за того, что она обеспечивает точку «абсолютного нуля», то есть температуру, при которой все тепловое движение всех внутренних частицы прекращаются. Нам просто нужно преобразовать это в градусы Фаренгейта или Цельсия с помощью следующих формул:

Ф = К * 9/5 - 459,67
С = К - 273,15

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

 axios.get(ENTIRE_API_URL) .then(response => { // Getting the current temperature and the city from the response object. const kelvinTemperature = response.data.main.temp; const cityName = response.data.name; const countryName = response.data.sys.country; // Making K to F and K to C conversions. const fahrenheitTemperature = (kelvinTemperature * 9/5) — 459.67; const celciusTemperature = kelvinTemperature — 273.15; // Building the final message. const message = ( `Right now, in \ ${cityName}, ${countryName} the current temperature is \ ${fahrenheitTemperature.toFixed(2)} deg F or \ ${celciusTemperature.toFixed(2)} deg C.`.replace(/\s+/g, ' ') ); console.log(message); }) .catch(error => console.log('Error', error));

Круглые скобки вокруг переменной message не обязательны, они просто красиво выглядят — как при работе с JSX в React. Обратная косая черта не позволяет строке шаблона форматировать новую строку, а метод прототипа replace() String избавляется от пробелов с помощью регулярных выражений (RegEx). Методы прототипа toFixed() Number округляют число с плавающей запятой до определенного количества знаков после запятой — в данном случае до двух.

При этом наш окончательный app.js выглядит следующим образом:

 const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`; axios.get(ENTIRE_API_URL) .then(response => { // Getting the current temperature and the city from the response object. const kelvinTemperature = response.data.main.temp; const cityName = response.data.name; const countryName = response.data.sys.country; // Making K to F and K to C conversions. const fahrenheitTemperature = (kelvinTemperature * 9/5) — 459.67; const celciusTemperature = kelvinTemperature — 273.15; // Building the final message. const message = ( `Right now, in \ ${cityName}, ${countryName} the current temperature is \ ${fahrenheitTemperature.toFixed(2)} deg F or \ ${celciusTemperature.toFixed(2)} deg C.`.replace(/\s+/g, ' ') ); console.log(message); }) .catch(error => console.log('Error', error));

Заключение

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

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

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

Дальнейшее чтение на SmashingMag:

  • Понимание и использование REST API
  • Новые функции JavaScript, которые изменят способ написания регулярных выражений
  • Поддержание скорости Node.js: инструменты, методы и советы по созданию высокопроизводительных серверов Node.js
  • Создание простого чат-бота с искусственным интеллектом с помощью Web Speech API и Node.js