Node 入門:API、HTTP 和 ES6+ JavaScript 簡介

已發表: 2022-03-10
快速總結 ↬後端 Web 應用程序開發流程介紹 — 討論前沿 ES6+ JavaScript 功能、超文本傳輸協議、使用 API 和 JSON,以及使用 Node.js 構建快速且可擴展的後端。

您可能聽說過 Node.js 是“基於 Chrome 的 V8 JavaScript 引擎構建的異步 JavaScript 運行時”,並且它“使用事件驅動的非阻塞 I/O 模型,使其輕量且高效”。 但對某些人來說,這並不是最好的解釋。

什麼是節點? Node“異步”究竟意味著什麼,它與“同步”有何不同? “事件驅動”和“非阻塞”到底是什麼意思,Node 如何適應應用程序、互聯網網絡和服務器的大局?

我們將在本系列中嘗試回答所有這些問題以及更多問題,因為我們將深入了解 Node 的內部工作原理,了解超文本傳輸協議、API 和 JSON,並使用構建我們自己的 Bookshelf API MongoDB、Express、Lodash、Mocha 和 Handlebars。

什麼是 Node.js

Node 只是一個環境或運行時,在瀏覽器之外運行普通的 JavaScript(略有不同)。 我們可以使用它來構建桌面應用程序(使用 Electron 等框架)、編寫 Web 或應用程序服務器等等。

跳躍後更多! 繼續往下看↓

阻塞/非阻塞和同步/異步

假設我們正在調用數據庫以檢索有關用戶的屬性。 這個調用需要時間,如果請求是“阻塞的”,那麼這意味著它將阻塞我們程序的執行,直到調用完成。 在這種情況下,我們發出了一個“同步”請求,因為它最終阻塞了線程。

因此,同步操作會阻塞進程或線程,直到該操作完成,使線程處於“等待狀態”。 另一方面,異步操作是非阻塞的。 它允許線程的執行繼續進行,而不管操作完成所需的時間或完成的結果,並且線程的任何部分都不會在任何時候陷入等待狀態。

讓我們看另一個阻塞線程的同步調用示例。 假設我們正在構建一個應用程序,它比較兩個 Weather API 的結果以找出它們的溫度差異百分比。 我們以阻塞方式調用 Wea​​ther API One 並等待結果。 一旦我們得到結果,我們調用 Wea​​ther API 2 並等待它的結果。 如果您不熟悉 API,請不要擔心。 我們將在接下來的部分中介紹它們。 現在,只需將 API 視為兩台計算機可以相互通信的媒介。

描述同步操作需要很長時間才能完成的圖形
同步阻塞操作的時間進展(大預覽)

請允許我注意,重要的是要認識到並非所有同步調用都一定是阻塞的。 如果同步操作可以在不阻塞線程或導致等待狀態的情況下完成,則它是非阻塞的。 大多數情況下,同步調用會被阻塞,它們完成所需的時間將取決於多種因素,例如 API 服務器的速度、最終用戶的 Internet 連接下載速度等。

在上圖的情況下,我們不得不等待很長時間才能從 API One 中檢索第一個結果。 此後,我們必須等待同樣長的時間才能從 API 2 獲得響應。 在等待這兩個響應時,用戶會注意到我們的應用程序掛起——UI 會直接鎖定——這對用戶體驗不利。

在非阻塞調用的情況下,我們會有這樣的事情:

描述異步非阻塞操作幾乎快 50% 的圖形
異步非阻塞操作的時間進展(大預覽)

您可以清楚地看到我們完成執行的速度有多快。 與其在 API 1 上等待,然後在 API 2 上等待,我們可以等待它們同時完成並以近 50% 的速度獲得我們的結果。 注意,一旦我們調用 API One 並開始等待它的響應,我們也調用 API 2 並與 API 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(...)時,您會注意到我們為方法定義中的回調參數提供了一個回調函數。 這個函數是一個匿名函數(它沒有名字)並且是使用Expression Syntax編寫的。 另一方面,方法定義是一個函數語句。 它不是匿名的,因為它實際上有一個名稱(即“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 ,我們只是說停止函數執行,因此如果定義了錯誤對象,我們就不會打印數據對象。 我們也可以將 log 語句包裝在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.

您可以看到第一個日誌語句按預期運行。 瞬間,最後一條日誌語句打印到屏幕上,因為這發生在第二個setTimeout(...)之後超過 0 秒之前。 緊接著,第二個、第三個和第一個setTimeout(...)方法執行。

如果 Node.js 不是非阻塞的,我們會看到第一個日誌語句,等待 3 秒才能看到下一個,立即看到第三個(0 秒setTimeout(...) ,然後必須再等待一個秒看最後兩條日誌語句。Node 的非阻塞特性使所有計時器從程序執行的那一刻開始倒計時,而不是按鍵入的順序倒計時。您可能需要查看 Node API,the調用堆棧和事件循環以獲取有關 Node 如何在後台工作的更多信息。

需要注意的是,僅僅因為您看到回調函數並不一定意味著代碼中有異步調用。 我們將上面的asyncAddFunction(…)方法稱為“async”,因為我們假設該操作需要時間才能完成——例如調用服務器。 實際上,將兩個數字相加的過程不是異步的,因此這實際上是一個使用回調函數的示例,它實際上不會阻塞線程。

回調的承諾

回調在 JavaScript 中很快就會變得混亂,尤其是多個嵌套的回調。 我們熟悉將回調作為參數傳遞給函數,但 Promise 允許我們將回調附加到從函數返回的對像上。 這將使我們能夠以更優雅的方式處理多個異步調用。

例如,假設我們正在進行 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 調用,我們將不得不嵌套兩個回調。 假設我需要將res1對像中的userName屬性注入到第二個 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); }); });

注意注入res1.userName屬性而不是字符串連接的 ES6+ 方法是使用“模板字符串”。 這樣,我們將使用反引號( ` )而不是用引號( '" )封裝我們的字符串。位於鍵盤上的 Escape 鍵下方。然後,我們將使用符號${}將任何 JS 表達式嵌入其中最後,我們之前的路徑是: /newExample/${res.UserName} ,用反引號括起來。

很明顯,這種嵌套回調的方法很快就會變得非常不優雅,即所謂的“JavaScript 末日金字塔”。 進入,如果我們使用 Promise 而不是回調,我們可以從第一個示例中重構我們的代碼,如下所示:

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

then()函數的第一個參數是我們的成功回調,第二個參數是我們的失敗回調。 或者,我們可以將第二個參數丟失給 .then( .then() ,並改為調用.catch() 。 .then( .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()調用並期望它能夠工作。 我們調用的函數必須實際返回一個 Promise,一個 Promise 會在異步操作完成時觸發.then() 。 在這種情況下, makeAPICall(...)會做它的事情,完成時觸發then()塊或catch()塊。

為了使makeAPICall(...)返回一個 Promise,我們將一個函數分配給一個變量,其中該函數是 Promise 構造函數。 Promise 可以被執行或被拒絕,其中被執行意味著與承諾相關的動作成功完成,被拒絕意味著相反。 一旦承諾被履行或被拒絕,我們就說它已經解決,在等待它解決的時候,也許在異步調用期間,我們說承諾是未決的。

Promise 構造函數接受一個回調函數作為參數,它接收兩個參數resolvereject ,我們將在稍後調用它們來觸發.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. }); }

Promise 確實可以通過“Promise Chaining”的概念來改進我們的代碼的結構,進而提高代碼的優雅性。 這將允許我們在.then()子句中返回一個新的 Promise,因此我們可以在其後附加第二個.then() ,這將從第二個 Promise 觸發適當的回調。

用 Promises 重構我們上面的多 API URL 調用,我們得到:

 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 原則(Don't Repeat Yourself),只需要實現一次錯誤處理。

 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()對應的單個 Promise 的狀態觸發。 但是, catch塊將捕獲在任何.then()中觸發的任何錯誤。

ES6 Const vs. Let

在我們所有的示例中,我們一直在使用 ES5 函數和舊的var關鍵字。 雖然今天仍有數百萬行代碼使用這些 ES5 方法運行,但更新到當前的 ES6+ 標準很有用,我們將重構上面的一些代碼。 讓我們從constlet開始。

您可能習慣於使用var關鍵字聲明變量:

 var pi = 3.14;

使用 ES6+ 標準,我們可以做到這一點

let pi = 3.14;

要么

const pi = 3.14;

其中const表示“常量”——一個以後不能重新分配的值。 (除了對象屬性——我們很快就會介紹。另外,聲明為const的變量不是不可變的,只有對變量的引用是。)

在舊的 JavaScript 中,塊作用域,例如ifwhile{}中的那些。 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

這裡要注意的重要一點是,在for範圍內定義一個新的var num直接影響了for外部和之上的var num 。 這是因為var的範圍始終是封閉函數的範圍,而不是塊。

同樣,默認情況下, for()中的var i默認為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 函數一樣,我們可以接受帶括號的參數,我們可以使用正常的 return 語句,我們可以像調用其他函數一樣調用函數。

需要注意的是,如果我們的函數不帶參數(如上面的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()將返回“Hi. 我叫約翰·多伊。” 相反,我們得到:“嗨。 我的名字不詳。” 這是因為箭頭函數沒有this ,因此嘗試在箭頭函數內部使用this默認為封閉範圍的this ,而Person對象的封閉範圍是window ,在瀏覽器中,或module.exports在節點。

為了證明這一點,如果我們再次使用同一個對象,但將全局thisname屬性設置為類似於“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 為使用classnew關鍵字的類提供支持:

 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#)經驗的讀者都應該熟悉這一點。

無需過多介紹 OOP 概念,另一種這樣的範式是“繼承”,即允許一個類從另一個類繼承。 例如,一個名為Car的類將非常通用——包含所有汽車都需要的“stop”、“start”等方法。 那麼,一個名為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 事件模型。

我們可以使用 Node 中的events模塊來發出和響應特定事件。 任何發出事件的對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');

通過使用繼承,我們可以將 'EventEmitter' 的emit()on()方法暴露給任何類。 這是通過創建一個 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()方法。 這已被 ES6 Classes 和extends棄用。

節點包管理器

使用 Node 和 JavaScript 進行編程時,經常會聽到npm 。 Npm 是一個包管理器,它可以做到這一點——允許下載解決 JavaScript 中常見問題的第三方包。 Yarn、Npx、Grunt 和 Bower 等其他解決方案也存在,但在本節中,我們將只關注npm以及如何使用它通過簡單的命令行界面 (CLI) 為應用程序安裝依賴項。

讓我們從簡單的開始,只npm 。 訪問 NpmJS 主頁以查看 NPM 提供的所有包。 當您啟動一個將依賴於 NPM 包的新項目時,您必須通過項目根目錄中的終端運行npm init 。 您將被問到一系列問題,這些問題將用於創建package.json文件。 此文件存儲您的所有依賴項——應用程序依賴於運行的模塊、腳本——用於運行測試、構建項目、啟動開發服務器等的預定義終端命令,等等。

要安裝一個包,只需運行npm install [package-name] --savesave標誌將確保包及其版本記錄在package.json文件中。 從npm版本 5 開始,默認情況下會保存依賴項,因此--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]

您還可以全局安裝軟件包。 該軟件包將適用於所有項目,而不僅僅是您正在處理的項目。 您可以在npm i [package-name]之後使用-g標誌執行此操作。 這通常用於 CLI,例如 Google Firebase 和 Heroku。 儘管這種方法很容易,但通常認為全局安裝包是不好的做法,因為它們沒有保存在package.json文件中,如果其他開發人員嘗試使用您的項目,他們將無法獲得所有必需的依賴項npm install

API 和 JSON

API 是編程中非常常見的範例,即使您剛剛開始作為開發人員的職業生涯,API 及其使用,尤其是在 Web 和移動開發中,也可能會經常出現。

API是一種應用程序編程接口,它基本上是兩個解耦系統可以相互通信的方法。 在更專業的術語中,API 允許系統或計算機程序(通常是服務器)接收請求並發送適當的響應(到客戶端,也稱為主機)。

假設您正在構建一個天氣應用程序。 您需要一種將用戶地址地理編碼為緯度和經度的方法,然後需要一種方法來獲得該特定位置的當前或預測天氣。

作為開發人員,您希望專注於構建您的應用程序並將其貨幣化,而不是將基礎設施到位以對地址進行地理編碼或在每個城市放置氣象站。

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 的 Create 部分呢? 好吧,當談到 HTTP 請求時,這就是所謂的 POST 請求。 就像您可以在社交媒體平台上發布消息一樣,您也可以將新記錄發佈到數據庫中。

CRUD 的更新允許我們使用 PUT 或 PATCH 請求來更新資源。 HTTP 的 PUT 將創建一條新記錄或更新/替換舊記錄。

讓我們更詳細地看一下,然後我們將進入 PATCH。

API 通常通過向 URL 中的特定路由發出 HTTP 請求來工作。 假設我們正在製作一個 API 來與包含用戶書單的數據庫對話。 然後我們也許可以在 URL .../books上查看這些書。 對.../books的 POST 請求將使用您在.../books路由中定義的任何屬性(想想 id、標題、ISBN、作者、發布數據等)創建一本新書。 現在將所有書籍存儲在.../books的底層數據結構是什麼並不重要。 我們只關心 API 公開那個端點(通過路由訪問)來操作數據。 前面的句子很關鍵:一個 POST 請求在...books/路由上創建一本新書。 因此,PUT 和 POST 之間的區別在於,如果不存在這樣的書,PUT 將創建一本新書(與 POST 一樣),或者,如果該書已存在於上述數據結構中,它將替換現有書

假設每本書具有以下屬性:id、title、ISBN、author、hasRead(布爾值)。

然後添加一本新書,如前所述,我們將向.../books發出 POST 請求。 如果我們想完全更新或替換一本書,我們將向.../books/id發出 PUT 請求,其中id是我們要替換的書的 ID。

雖然 PUT 完全替換了現有書籍,但 PATCH 更新了與特定書籍有關的內容,可能會修改我們在上面定義的hasRead布爾屬性——因此我們會向…/books/id發出 PATCH 請求,發送新數據。

現在可能很難理解它的含義,因為到目前為止,我們已經在理論上建立了一切,但還沒有看到任何實際發出 HTTP 請求的有形代碼。 但是,我們很快就會談到這一點,本文將介紹 GET,其餘部分將在以後的文章中介紹。

最後一個基本的 CRUD 操作稱為刪除。 如您所料,這種 HTTP 請求的名稱是“DELETE”,它的工作方式與 PATCH 非常相似,需要在路由中提供圖書的 ID。

到目前為止,我們已經了解到,路由是您向其發出 HTTP 請求的特定 URL,並且端點是 API 提供的函數,對它公開的數據做一些事情。 也就是說,端點是位於路由另一端的編程語言函數,它執行您指定的任何 HTTP 請求。 我們還了解到,存在諸如 POST、GET、PUT、PATCH、DELETE 等術語(稱為 HTTP 動詞),它們實際上指定了您對 API 發出的請求。 與 JSON 一樣,這些 HTTP 請求方法是由 Internet 工程任務組 (IETF) 定義的 Internet 標準,最值得注意的是 RFC 7231,第 4 節:請求方法和 RFC 5789,第 2 節:補丁方法,其中 RFC 是徵求意見。

因此,我們可能會向 URL .../books/id發出 GET 請求,其中傳入的 ID 稱為參數。 我們可以向.../books發出 POST、PUT 或 PATCH 請求來創建資源,或者向.../books/id發出修改/替換/更新資源的請求。 我們還可以向.../books/id發出 DELETE 請求以刪除特定書籍。

可以在此處找到 HTTP 請求方法的完整列表。

還需要注意的是,在發出 HTTP 請求後,我們會收到響應。 具體響應取決於我們如何構建 API,但您應該始終收到狀態碼。 之前,我們說過,當您的網絡瀏覽器向網絡服務器請求 HTML 時,它會以“OK”響應。 這稱為 HTTP 狀態代碼,更具體地說,是 HTTP 200 OK。 狀態碼僅指定端點中指定的操作或動作(請記住,這是我們完成所有工作的函數)如何完成。 HTTP 狀態碼是服務器發回的,可能有很多你熟悉的,比如 404 Not Found(找不到資源或文件,這就像向.../books/id發出 GET 請求一樣) .../books/id不存在這樣的 ID。)

可以在此處找到 HTTP 狀態代碼的完整列表。

MongoDB

MongoDB 是一種類似於 Firebase 實時數據庫的非關係型 NoSQL 數據庫。 您將通過 Node 包(例如 MongoDB Native Driver 或 Mongoose)與數據庫通信。

在 MongoDB 中,數據存儲在 JSON 中,這與 MySQL、PostgreSQL 或 SQLite 等關係數據庫有很大不同。 兩者都稱為數據庫,SQL 表稱為集合,SQL 表行稱為文檔,SQL 表列稱為字段。

當我們創建我們的第一個 Bookshelf API 時,我們將在本系列的下一篇文章中使用 MongoDB 數據庫。 上面列出的基本 CRUD 操作可以在 MongoDB 數據庫上執行。

建議您通讀 MongoDB 文檔以了解如何在 Atlas Cluster 上創建實時數據庫並使用 MongoDB Native Driver 對其進行 CRUD 操作。 在本系列的下一篇文章中,我們將學習如何設置本地數據庫和雲生產數據庫。

構建命令行節點應用程序

在構建應用程序時,您會看到許多作者在文章開頭轉儲了他們的整個代碼庫,然後嘗試解釋其後的每一行。 在本文中,我將採用不同的方法。 我將逐行解釋我的代碼,同時構建應用程序。 我不會擔心模塊化或性能,不會將代碼庫拆分為單獨的文件,也不會遵循 DRY 原則或嘗試使代碼可重用。 剛學習時,讓事情盡可能簡單是很有用的,這就是我將在這裡採用的方法。

讓我們清楚我們正在構建什麼。 我們不會關心用戶輸入,所以我們不會使用像 Yargs 這樣的包。 我們也不會構建自己的 API。 當我們使用 Express Web 應用程序框架時,這將在本系列的後續文章中介紹。 我採用這種方法是為了不將 Node.js 與 Express 和 API 的強大功能混為一談,因為大多數教程都是這樣做的。 相反,我將提供一種(多種)方法來調用和接收來自使用第三方 JavaScript 庫的外部 API 的數據。 我們將調用的 API 是 Weather API,我們將從 Node 訪問它並將其輸出轉儲到終端,可能帶有一些格式,稱為“漂亮打印”。 我將介紹整個過程,包括如何設置 API 和獲取 API 密鑰,其步驟提供了截至 2019 年 1 月的正確結果。

我們將為此項目使用 OpenWeatherMap API,因此要開始使用,請導航到 OpenWeatherMap 註冊頁面並使用表單創建一個帳戶。 登錄後,在儀表板頁面上找到 API Keys 菜單項(位於此處)。 如果您剛剛創建了一個帳戶,則必須為您的 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 Key 作為查詢參數:

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

現在,將該 URL 複製到 Web 瀏覽器的新選項卡中,將{YOUR_API_KEY}佔位符替換為您之前註冊帳戶時獲得的 API 密鑰。

您可以看到的文本實際上是 JSON — 如前所述,這是公認的網絡語言。

要進一步檢查,請在 Google Chrome 中按 Ctrl + Shift + I打開 Chrome 開發人員工具,然後導航到網絡選項卡。 目前這裡應該沒有數據。

空的 Chrome 開發工具網絡選項卡
空的谷歌瀏覽器開發者工具(大預覽)

要實際監控網絡數據,請重新加載頁面,然後觀察選項卡中是否填充了有用的信息。 單擊下圖所示的第一個鏈接。

填充的 Chrome 開發工具網絡選項卡
填充的 Google Chrome 開發者工具(大預覽)

單擊該鏈接後,我們實際上可以查看 HTTP 特定信息,例如標頭。 標頭在 API 的響應中發送(在某些情況下,您還可以將自己的標頭髮送到 API,或者您甚至可以創建自己的自定義標頭(通常以x-為前綴)以在構建自己的 API 時發回),並且只包含客戶端或服務器可能需要的額外信息。

在這種情況下,您可以看到我們向 API 發出了 HTTP GET 請求,它以 HTTP 狀態 200 OK 響應。 您還可以看到發回的數據採用 JSON 格式,如“響應標頭”部分中所列。

描述來自 API 響應的標頭的 Google 開發工具
API 響應中的標頭(大預覽)

如果您點擊預覽選項卡,您實際上可以將 JSON 視為 JavaScript 對象。 您可以在瀏覽器中看到的文本版本是一個字符串,因為 JSON 始終作為字符串在 Web 上傳輸和接收。 這就是為什麼我們必須在我們的代碼中解析 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文件夾已成功創建。

在瀏覽器中,您可以看到我們通過在 URL 欄中手動輸入正確的 URL 手動發出了 GET 請求。 Axios 可以讓我在 Node.js 中做到這一點。

從現在開始,以下所有代碼都將位於app.js文件中,每個片段一個接一個地放置。

我要做的第一件事是需要我們之前安裝的 Axios 包

const axios = require('axios');

我們現在可以訪問 Axios,並且可以通過axios常量發出相關的 HTTP 請求。

通常,我們的 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向該 URL 發出 GET 請求。 為此,我們將使用axios提供的get(url)方法。

 axios.get(ENTIRE_API_URL)

axios.get(...)實際上返回一個 Promise,並且成功回調函數將接受一個響應參數,這將允許我們訪問來自 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)

現在,我們說過 Web 服務器總是將 JSON 作為字符串處理,這是真的。 但是,您可能會注意到response.data已經是一個對象(通過運行console.log(typeof response.data)可以看出)——我們不必使用JSON.parse()解析它。 那是因為 Axios 已經在幕後為我們解決了這個問題。

通過運行console.log(JSON.stringify(response.data, undefined, 2))終端中運行console.log(response.data)的輸出可以被格式化——“漂亮打印”。 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 }

現在,很明顯我們要查找的溫度位於response.data對象的main屬性上,因此我們可以通過調用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));

我們得到的溫度實際上是以開爾文為單位的,這是物理學、化學和熱力學中普遍使用的溫度標度,因為它提供了一個“絕對零”點,即所有內部所有熱運動的溫度。粒子停止。 我們只需使用以下公式將其轉換為華氏溫度或攝氏度:

F = K * 9/5 - 459.67
C = K - 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變量周圍的括號不是必需的,它們只是看起來不錯——類似於在 React 中使用 JSX 時。 反斜杠阻止模板字符串格式化新行,並且replace()字符串原型方法使用正則表達式 (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 構建一個簡單的 AI 聊天機器人