เริ่มต้นใช้งานโหนด: บทนำสู่ API, HTTP และ ES6+ JavaScript

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ บทนำเกี่ยวกับกระบวนการพัฒนาเว็บแอปพลิเคชันแบ็กเอนด์ — พูดคุยถึงฟีเจอร์ ES6+ JavaScript ที่รั่วไหล, HyperText Transfer Protocol, การทำงานกับ API และ JSON และการใช้ Node.js เพื่อสร้างแบ็กเอนด์ที่รวดเร็วและปรับขนาดได้

คุณคงเคยได้ยินเกี่ยวกับ Node.js ว่าเป็น “รันไทม์ JavaScript แบบอะซิงโครนัสที่สร้างขึ้นบนเอ็นจิ้น V8 JavaScript ของ Chrome” และว่า “ใช้โมเดล I/O ที่ขับเคลื่อนด้วยเหตุการณ์และไม่บล็อกซึ่งทำให้มีน้ำหนักเบาและมีประสิทธิภาพ” แต่สำหรับบางคน นั่นไม่ใช่คำอธิบายที่ยิ่งใหญ่ที่สุด

โหนดในตอนแรกคืออะไร? โหนดเป็น "อะซิงโครนัส" หมายความว่าอย่างไร และแตกต่างจาก "ซิงโครนัส" อย่างไร ความหมายของ "เหตุการณ์ที่ขับเคลื่อน" และ "การไม่ปิดกั้น" คืออะไร และโหนดเหมาะสมกับภาพรวมของแอปพลิเคชัน เครือข่ายอินเทอร์เน็ต และเซิร์ฟเวอร์อย่างไร

เราจะพยายามตอบคำถามเหล่านี้ทั้งหมดและอื่นๆ ตลอดทั้งชุดข้อมูลนี้เมื่อเราเจาะลึกถึงการทำงานภายในของ Node เรียนรู้เกี่ยวกับ HyperText Transfer Protocol, API และ JSON และสร้าง Bookshelf API ของเราเองโดยใช้ MongoDB, Express, Lodash, Mocha และ Handlebars

Node.js คืออะไร

โหนดเป็นเพียงสภาพแวดล้อมหรือรันไทม์ ซึ่งใช้ JavaScript ปกติ (มีความแตกต่างเล็กน้อย) ภายนอกเบราว์เซอร์ เราสามารถใช้เพื่อสร้างแอปพลิเคชันเดสก์ท็อป (ด้วยเฟรมเวิร์กเช่น Electron) เขียนเว็บหรือเซิร์ฟเวอร์แอป และอื่นๆ

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

การบล็อก/ไม่บล็อกและซิงโครนัส/อะซิงโครนัส

สมมติว่าเรากำลังทำการเรียกฐานข้อมูลเพื่อดึงคุณสมบัติเกี่ยวกับผู้ใช้ การโทรนั้นต้องใช้เวลา และหากคำขอนั้น "ปิดกั้น" นั่นหมายความว่าจะบล็อกการทำงานของโปรแกรมของเราจนกว่าการโทรจะเสร็จสิ้น ในกรณีนี้ เราได้ทำการร้องขอ "ซิงโครนัส" เนื่องจากมันจบลงด้วยการบล็อกเธรด

ดังนั้น การดำเนินการแบบ ซิงโครนั สจะ บล็อก กระบวนการหรือเธรดจนกว่าการดำเนินการนั้นจะเสร็จสิ้น โดยปล่อยให้เธรดอยู่ใน "สถานะรอ" ในทางกลับกัน การดำเนินการ แบบอะซิงโครนัส จะ ไม่บล็อก อนุญาตให้ดำเนินการเธรดเพื่อดำเนินการต่อโดยไม่คำนึงถึงเวลาที่ใช้ในการดำเนินการให้เสร็จสิ้นหรือผลลัพธ์เสร็จสิ้น และไม่มีส่วนใดของเธรดตกอยู่ในสถานะรอ ณ จุดใด ๆ

ลองดูตัวอย่างอื่นของการโทรแบบ ซิงโครนั สที่ บล็อก เธรด สมมติว่าเรากำลังสร้างแอปพลิเคชันที่เปรียบเทียบผลลัพธ์ของ Weather API สองรายการเพื่อค้นหาเปอร์เซ็นต์ความแตกต่างของอุณหภูมิ ในลักษณะการบล็อก เราเรียก Weather API One และรอผล เมื่อได้ผลลัพธ์แล้ว เราจะเรียก Weather API Two และรอผลของมัน อย่ากังวลในตอนนี้หากคุณไม่คุ้นเคยกับ API เราจะกล่าวถึงในส่วนต่อไป ในตอนนี้ ลองนึกถึง API เป็นสื่อกลางที่คอมพิวเตอร์สองเครื่องสามารถสื่อสารกันได้

ภาพกราฟิกแสดงความจริงที่ว่า Synchronous Operations ใช้เวลานานกว่าจะเสร็จสมบูรณ์
ความคืบหน้าของเวลาของการดำเนินการบล็อกแบบซิงโครนัส (ตัวอย่างขนาดใหญ่)

ให้ฉันทราบ สิ่งสำคัญคือต้องตระหนักว่าการโทรแบบซิงโครนัสบางสายไม่จำเป็นต้องปิดกั้น หากการดำเนินการแบบซิงโครนัสสามารถจัดการให้เสร็จสิ้นได้โดยไม่ต้องบล็อกเธรดหรือทำให้อยู่ในสถานะรอ แสดงว่าการดำเนินการนั้นไม่บล็อก โดยส่วนใหญ่ การเรียกแบบซิงโครนัสจะถูกบล็อก และเวลาที่ใช้ในการดำเนินการจะขึ้นอยู่กับปัจจัยหลายประการ เช่น ความเร็วของเซิร์ฟเวอร์ของ API ความเร็วในการดาวน์โหลดการเชื่อมต่ออินเทอร์เน็ตของผู้ใช้ปลายทาง เป็นต้น

ในกรณีของรูปภาพด้านบน เราต้องรอสักครู่เพื่อดึงผลลัพธ์แรกจาก API One หลังจากนั้น เราต้องรอเท่าๆ กันเพื่อรับการตอบกลับจาก API สอง ระหว่างรอคำตอบทั้งสอง ผู้ใช้จะสังเกตเห็นว่าแอปพลิเคชันของเราหยุดทำงาน — UI จะถูกล็อคอย่างแท้จริง — และนั่นจะไม่ดีต่อประสบการณ์ของผู้ใช้

ในกรณีของการโทรแบบไม่บล็อค เราจะมีลักษณะดังนี้:

กราฟิกที่แสดงให้เห็นข้อเท็จจริงที่ว่า Asynchronous Non-Blocking Operations นั้นเร็วขึ้นเกือบ 50 เปอร์เซ็นต์
ความคืบหน้าของเวลาของการดำเนินการที่ไม่บล็อกแบบอะซิงโครนัส (ตัวอย่างขนาดใหญ่)

คุณสามารถเห็นได้อย่างชัดเจนว่าเราสรุปการดำเนินการได้เร็วแค่ไหน แทนที่จะรอ API One แล้วรอ API Two เราสามารถรอให้ทั้งคู่ทำงานให้เสร็จพร้อมกันและบรรลุผลลัพธ์เร็วขึ้นเกือบ 50% สังเกตว่า เมื่อเราเรียก API One และเริ่มรอการตอบกลับ เราก็เรียก API Two และเริ่มรอการตอบสนองพร้อมกันกับ One

ณ จุดนี้ ก่อนที่จะพูดถึงตัวอย่างที่เป็นรูปธรรมและจับต้องได้ สิ่งสำคัญคือต้องกล่าวว่า เพื่อให้ง่าย คำว่า "ซิงโครนัส" โดยทั่วไปจะย่อให้สั้นลงเป็น "ซิงค์" และคำว่า "อะซิงโครนัส" โดยทั่วไปจะย่อเป็น "อะซิงโครนัส" คุณจะเห็นสัญลักษณ์นี้ใช้ในชื่อเมธอด/ฟังก์ชัน

ฟังก์ชั่นการโทรกลับ

คุณอาจจะสงสัยว่า “ถ้าเราสามารถจัดการการโทรแบบอะซิงโครนัสได้ เราจะรู้ได้อย่างไรว่าการโทรนั้นสิ้นสุดเมื่อใดและมีการตอบกลับมา” โดยทั่วไป เราส่งผ่านฟังก์ชันเรียกกลับเป็นอาร์กิวเมนต์ไปยังเมธอด async ของเรา และเมธอดนั้นจะ "โทรกลับ" ที่ทำงานในภายหลังพร้อมการตอบกลับ ฉันใช้ฟังก์ชัน 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. });

ฟังก์ชันดังกล่าวเรียกว่า "ฟังก์ชันลำดับที่สูงกว่า" เนื่องจากใช้ฟังก์ชัน (เรียกกลับของเรา) เป็นอาร์กิวเมนต์ อีกทางหนึ่ง ฟังก์ชันเรียกกลับอาจรับวัตถุข้อผิดพลาดและวัตถุตอบสนองเป็นอาร์กิวเมนต์ และนำเสนอเมื่อฟังก์ชัน async เสร็จสมบูรณ์ เราจะเห็นสิ่งนี้ในภายหลังด้วย Express เมื่อเราเรียก asyncAddFunction(...) คุณจะสังเกตเห็นว่าเราได้จัดเตรียมฟังก์ชันการเรียกกลับสำหรับพารามิเตอร์การเรียกกลับจากการกำหนดวิธีการ ฟังก์ชันนี้เป็นฟังก์ชันที่ ไม่ระบุชื่อ (ไม่มีชื่อ) และเขียนโดยใช้ ไวยากรณ์นิพจน์ ในทางกลับกัน นิยามเมธอดเป็นคำสั่งฟังก์ชัน ไม่เป็นนิรนามเพราะมีชื่อจริง (ที่เป็น “asyncAddFunction”)

บางคนอาจสังเกตเห็นความสับสน เนื่องจากในคำจำกัดความของเมธอด เราระบุชื่อที่เป็น "การโทรกลับ" อย่างไรก็ตาม ฟังก์ชันที่ไม่ระบุชื่อที่ส่งผ่านเนื่องจากพารามิเตอร์ตัวที่สามไปยัง asyncAddFunction(...) ไม่ทราบชื่อ ดังนั้นจึงไม่ระบุชื่อ เรายังไม่สามารถเรียกใช้ฟังก์ชันนั้นในภายหลังโดยใช้ชื่อ เราต้องผ่านฟังก์ชันการเรียก async อีกครั้งเพื่อเริ่มการทำงาน

ตัวอย่างของการเรียกแบบซิงโครนัส เราสามารถใช้เมธอด 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); }

ให้เราดูการใช้งานการเรียกใช้ฟังก์ชัน async ครั้งสุดท้าย สิ่งนี้จะช่วยเสริมความแข็งแกร่งให้กับแนวคิดของฟังก์ชันเรียกกลับที่เริ่มทำงานในเวลาต่อมา และจะช่วยให้เราเข้าใจการทำงานของโปรแกรม 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 วินาทีเพื่อดูรายการถัดไป จากนั้นเห็นคำสั่งที่สามทันที ( setTimeout(...) 0 วินาที 0 วินาที แล้วต้องรออีกครั้ง วินาทีเพื่อดูคำสั่งบันทึก 2 รายการสุดท้าย ลักษณะการไม่บล็อกของ Node ทำให้ตัวจับเวลาทั้งหมดเริ่มนับถอยหลังจากช่วงเวลาที่โปรแกรมทำงาน แทนที่จะเป็นลำดับการพิมพ์ คุณอาจต้องการดู Node APIs Callstack และ Event Loop สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานของ Node ภายใต้ประทุน

สิ่งสำคัญคือต้องสังเกตว่าการที่คุณเห็นฟังก์ชันเรียกกลับไม่ได้หมายความว่ามีการเรียกแบบอะซิงโครนัสในโค้ดเสมอไป เราเรียก asyncAddFunction(…) ที่ด้านบน “async” เนื่องจากเราถือว่าการดำเนินการต้องใช้เวลาในการดำเนินการให้เสร็จสิ้น เช่น การเรียกไปยังเซิร์ฟเวอร์ ในความเป็นจริง กระบวนการเพิ่มตัวเลขสองตัวไม่ตรงกัน และนั่นจะเป็นตัวอย่างของการใช้ฟังก์ชันเรียกกลับในลักษณะที่ไม่บล็อกเธรดจริง ๆ

สัญญามากกว่าการโทรกลับ

การเรียกกลับอาจกลายเป็นเรื่องยุ่งเหยิงใน JavaScript ได้อย่างรวดเร็ว โดยเฉพาะอย่างยิ่งการเรียกกลับที่ซ้อนกันหลายครั้ง เราคุ้นเคยกับการส่งการเรียกกลับเป็นอาร์กิวเมนต์ของฟังก์ชัน แต่ Promises อนุญาตให้เราตรึง หรือแนบ การเรียกกลับไปยังวัตถุที่ส่งคืนจากฟังก์ชัน วิธีนี้จะช่วยให้เราจัดการการเรียก async หลายรายการได้อย่างสวยงามยิ่งขึ้น

ตัวอย่างเช่น สมมติว่าเรากำลังทำการเรียก 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 แทนการต่อสตริงจะใช้ "Template Strings" ด้วยวิธีนี้ แทนที่จะใส่เครื่องหมายคำพูด ( ' หรือ " ) เราจะใช้ backticks ( ` ) ที่อยู่ใต้แป้น Escape บนแป้นพิมพ์ของคุณ จากนั้น เราจะใช้สัญลักษณ์ ${} เพื่อฝังนิพจน์ JS ใดๆ ไว้ภายใน วงเล็บ ในท้ายที่สุด เส้นทางก่อนหน้าของเราคือ: /newExample/${res.UserName} ห่อด้วย backticks

เป็นที่ชัดเจนว่าวิธีการซ้อนการเรียกกลับแบบซ้อนนี้สามารถกลายเป็นสิ่งที่ไม่เป็นระเบียบได้อย่างรวดเร็ว ซึ่งเรียกว่า “JavaScript Pyramid of Doom” หากเราใช้คำสัญญาแทนการเรียกกลับ เราสามารถจัดโครงสร้างโค้ดของเราใหม่จากตัวอย่างแรกดังนี้:

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

อาร์กิวเมนต์แรกของฟังก์ชัน then() คือการโทรกลับที่ประสบความสำเร็จ และอาร์กิวเมนต์ที่สองคือการเรียกกลับที่ล้มเหลว อีกทางหนึ่ง เราอาจสูญเสียอาร์กิวเมนต์ที่สองไปที่ .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() เมื่อการดำเนินการ async นั้นเสร็จสมบูรณ์ ในกรณีนี้ makeAPICall(...) จะทำสิ่งนั้น โดยเริ่มต้นบล็อก then() หรือบล็อก catch() เมื่อเสร็จสิ้น

ในการทำให้ makeAPICall(...) คืนค่า Promise เรากำหนดฟังก์ชันให้กับตัวแปร โดยที่ฟังก์ชันนั้นคือ Promise Constructor สัญญาสามารถ สำเร็จ หรือ ถูกปฏิเสธ โดยที่การบรรลุผลหมายถึงการดำเนินการที่เกี่ยวข้องกับคำสัญญาที่สำเร็จลุล่วงไปด้วยดี และถูกปฏิเสธซึ่งมีความหมายตรงกันข้าม เมื่อสัญญาสำเร็จหรือถูกปฏิเสธ เราบอกว่าสัญญานั้นได้ ชำระ แล้ว และในขณะที่รอการตกลง บางทีระหว่างการโทรแบบ async เราบอกว่าสัญญานั้น กำลังรอดำเนิน การ

คอนสตรัคเตอร์ 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. }); }

สัญญาจะเปล่งประกายเพื่อปรับปรุงโครงสร้าง และต่อมาคือความสง่างามของโค้ดของเราด้วยแนวคิด "Promise Chaining" ซึ่งจะทำให้เราสามารถส่งคืน Promise ใหม่ภายใน .then() clause ดังนั้นเราจึงสามารถแนบ .then() ตัวที่สองได้หลังจากนั้น ซึ่งจะทำให้การเรียกกลับที่เหมาะสมจากคำสัญญาที่สองเริ่มทำงาน

การปรับโครงสร้างการเรียก URL แบบหลาย API ของเราด้านบนด้วย Promises เราได้รับ:

 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( .then() ใหม่หลังจากครั้งแรก

เช่นเดียวกับข้างต้น เราสามารถจัดโครงสร้างใหม่เพื่อให้อ่านง่าย และลบการเรียกกลับที่ล้มเหลวสำหรับคำสั่งทั่วไป catch() ทั้งหมด จากนั้น เราสามารถปฏิบัติตามหลักการ 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() เริ่มทำงานสำหรับสถานะของ Promise แต่ละรายการที่ .then() สอดคล้องเท่านั้น อย่างไรก็ตาม catch block จะตรวจจับข้อผิดพลาดใด ๆ ที่เกิดขึ้นใน .then() ใด ๆ

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 inside 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 เราสามารถรับอาร์กิวเมนต์ด้วยวงเล็บ เราสามารถใช้คำสั่ง return ปกติ และเราสามารถเรียกใช้ฟังก์ชันได้เหมือนกับฟังก์ชันอื่นๆ

สิ่งสำคัญที่ควรทราบคือ แม้ว่าวงเล็บจะต้องใช้หากฟังก์ชันของเราไม่มีอาร์กิวเมนต์ (เช่นเดียวกับ printHelloWorld() ด้านบน) เราสามารถวางวงเล็บได้หากใช้เพียงวงเล็บเดียว ดังนั้นคำจำกัดความเมธอด squareNumber() ก่อนหน้าของเราสามารถเขียนใหม่เป็น:

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

ไม่ว่าคุณจะเลือกสรุปอาร์กิวเมนต์เดียวในวงเล็บหรือไม่ก็ตาม เป็นเรื่องของรสนิยมส่วนตัว และคุณจะเห็นนักพัฒนาใช้ทั้งสองวิธี

สุดท้าย หากเราต้องการส่งคืนนิพจน์หนึ่งนิพจน์โดยปริยาย เช่นเดียวกับ squareNumber(...) ด้านบน เราสามารถใส่คำสั่ง return ให้สอดคล้องกับเมธอด signature:

 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(...) จากด้านบน สามารถ refactored จาก:

 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

สิ่งนี้เรียกว่า 'Lexical Scoping' และเราสามารถแก้ไขได้โดยใช้ 'Short Syntax' ซึ่งเป็นที่ที่เราสูญเสียเครื่องหมายทวิภาคและลูกศรเพื่อปรับโครงสร้างวัตถุของเราดังนี้:

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

คลาส ES6

แม้ว่าจาวาสคริปต์จะไม่รองรับคลาส แต่คุณก็สามารถเลียนแบบคลาสเหล่านั้นด้วยอ็อบเจกต์ในลักษณะที่กล่าวข้างต้นได้ 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#

โดยไม่ต้องลงรายละเอียดมากเกินไปเกี่ยวกับแนวคิด 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 keyword ช่วยให้เราเข้าถึงคุณสมบัติและเมธอดจาก parent หรือ super, class ได้

เหตุการณ์ JavaScript

เหตุการณ์คือการกระทำที่เกิดขึ้นซึ่งคุณมีความสามารถในการตอบสนอง สมมติว่าคุณกำลังสร้างแบบฟอร์มการเข้าสู่ระบบสำหรับใบสมัครของคุณ เมื่อผู้ใช้กดปุ่ม "ส่ง" คุณสามารถตอบสนองต่อเหตุการณ์นั้นผ่าน "ตัวจัดการเหตุการณ์" ในโค้ดของคุณ ซึ่งโดยทั่วไปแล้วเป็นฟังก์ชัน เมื่อฟังก์ชันนี้ถูกกำหนดเป็นตัวจัดการเหตุการณ์ เราบอกว่าเรากำลัง "ลงทะเบียนตัวจัดการเหตุการณ์" ตัวจัดการเหตุการณ์สำหรับการคลิกปุ่มส่งมักจะตรวจสอบการจัดรูปแบบของอินพุตที่ผู้ใช้ให้มา ฆ่าเชื้อเพื่อป้องกันการโจมตี เช่น SQL Injections หรือ Cross Site Scripting (โปรดทราบว่าไม่มีโค้ดใดในฝั่งไคลเอ็นต์ที่จะถูกพิจารณา ปลอดภัย ล้างข้อมูลบนเซิร์ฟเวอร์เสมอ - อย่าเชื่อถือสิ่งใดจากเบราว์เซอร์) จากนั้นตรวจสอบเพื่อดูว่าชื่อผู้ใช้และรหัสผ่านนั้นออกจากฐานข้อมูลเพื่อตรวจสอบสิทธิ์ผู้ใช้และให้บริการโทเค็นหรือไม่

เนื่องจากเป็นบทความเกี่ยวกับ Node เราจะเน้นที่ Node Event Model

เราสามารถใช้โมดูล 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');

โดยใช้การสืบทอด เราสามารถเปิดเผยเมธอด emit() และ on() จาก 'EventEmitter' ไปยังคลาสใดก็ได้ สิ่งนี้ทำได้โดยการสร้างคลาส Node.js และใช้คีย์เวิร์ดที่สงวนไว้เพื่อสืบทอดคุณสมบัติที่มีอยู่ EventEmitter extends

 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({ ... });

นี่เป็นตัวอย่างที่ซับซ้อนสำหรับเราสามารถใส่โค้ดลงในตัวจัดการเหตุการณ์ภายในฟังก์ชัน collide ของคลาสได้ แต่จะแสดงให้เห็นว่า Node Event Model ทำงานอย่างไร โปรดทราบว่าบทช่วยสอนบางบทจะแสดง util.inherits() ในการอนุญาตให้อ็อบเจ็กต์ปล่อยเหตุการณ์ ที่เลิกใช้แล้วในความโปรดปรานของ ES6 Classes และ extends .

ตัวจัดการแพ็คเกจโหนด

เมื่อเขียนโปรแกรมด้วย Node และ JavaScript เป็นเรื่องปกติที่จะได้ยินเกี่ยวกับ npm Npm เป็นตัวจัดการแพ็คเกจที่ทำอย่างนั้น — อนุญาตให้ดาวน์โหลดแพ็คเกจของบุคคลที่สามที่แก้ปัญหาทั่วไปใน JavaScript โซลูชันอื่นๆ เช่น Yarn, Npx, Grunt และ Bower ก็มีเช่นกัน แต่ในส่วนนี้ เราจะเน้นที่ npm เท่านั้น และวิธีที่คุณสามารถติดตั้งการพึ่งพาสำหรับแอปพลิเคชันของคุณผ่าน Command Line Interface (CLI) อย่างง่ายโดยใช้มัน

มาเริ่มกันง่ายๆ แค่ npm ไปที่หน้าแรกของ NpmJS เพื่อดูแพ็คเกจทั้งหมดที่มีจาก NPM เมื่อคุณเริ่มโปรเจ็กต์ใหม่ที่จะขึ้นอยู่กับแพ็คเกจ NPM คุณจะต้องรัน npm init ผ่านเทอร์มินัลในไดเร็กทอรีรากของโปรเจ็กต์ของคุณ คุณจะถูกถามคำถามหลายชุดซึ่งจะใช้ในการสร้างไฟล์ package.json ไฟล์นี้จัดเก็บการพึ่งพาของคุณทั้งหมด — โมดูลที่แอปพลิเคชันของคุณขึ้นอยู่กับการทำงาน สคริปต์ — คำสั่งเทอร์มินัลที่กำหนดไว้ล่วงหน้าเพื่อรันการทดสอบ สร้างโครงการ เริ่มเซิร์ฟเวอร์การพัฒนา ฯลฯ และอีกมากมาย

ในการติดตั้งแพ็คเกจเพียงเรียกใช้ npm install [package-name] --save แฟล็กการ save จะช่วยให้มั่นใจว่าแพ็กเกจและเวอร์ชันของแพ็กเกจได้รับการบันทึกในไฟล์ package.json ตั้งแต่ npm เวอร์ชัน 5 การพึ่งพาจะถูกบันทึกโดยค่าเริ่มต้น ดังนั้น --save อาจถูกละเว้น คุณจะสังเกตเห็นโฟลเดอร์ node_modules ใหม่ซึ่งมีรหัสสำหรับแพ็คเกจที่คุณเพิ่งติดตั้ง นอกจากนี้ยังสามารถย่อให้เหลือเพียง npm i [package-name] หมายเหตุที่เป็นประโยชน์ ไม่ควรรวมโฟลเดอร์ node_modules ไว้ในที่เก็บ GitHub เนื่องจากขนาดของโฟลเดอร์ เมื่อใดก็ตามที่คุณโคลน repo จาก GitHub (หรือระบบจัดการเวอร์ชันอื่น) อย่าลืมรันคำสั่ง npm install เพื่อออกไปและดึงแพ็คเกจทั้งหมดที่กำหนดไว้ในไฟล์ package.json สร้างไดเร็กทอรี node_modules โดยอัตโนมัติ คุณยังสามารถติดตั้งแพ็กเกจในเวอร์ชันเฉพาะได้ เช่น npm i [package-name]@1.10.1 --save

การลบแพ็คเกจคล้ายกับการติดตั้ง: npm remove [package-name]

คุณยังสามารถติดตั้งแพ็คเกจได้ทั่วโลก แพ็คเกจนี้จะใช้ได้กับทุกโครงการ ไม่ใช่แค่โครงการที่คุณทำงานอยู่ คุณทำสิ่งนี้ด้วยแฟล็ก -g หลังจาก npm i [package-name] โดยทั่วไปจะใช้สำหรับ CLI เช่น Google Firebase และ Heroku แม้ว่าวิธีนี้จะนำเสนอได้ง่าย แต่โดยทั่วไปถือว่าการติดตั้งแพ็กเกจทั่วโลกถือเป็นแนวทางปฏิบัติที่ไม่ดี เนื่องจากจะไม่ถูกบันทึกไว้ในไฟล์ package.json และหากนักพัฒนารายอื่นพยายามใช้โปรเจ็กต์ของคุณ พวกเขาจะไม่ได้รับการอ้างอิงที่จำเป็นทั้งหมดจาก npm install

API และ JSON

API เป็นกระบวนทัศน์ทั่วไปในการเขียนโปรแกรม และแม้ว่าคุณจะเพิ่งเริ่มต้นในสายงานนักพัฒนา แต่ API และการใช้งาน API โดยเฉพาะอย่างยิ่งในการพัฒนาเว็บและมือถือ มีแนวโน้มที่จะเกิดขึ้นบ่อยกว่าไม่

API คือ Application Programming Interface และโดยพื้นฐานแล้วเป็นวิธีการที่ระบบแยกส่วนสองระบบอาจสื่อสารกัน ในเงื่อนไขทางเทคนิคเพิ่มเติม 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? What then? 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" อย่างเหมาะสม การใช้อักษรตัวพิมพ์ใหญ่เป็นวิธีมาตรฐานในการแสดงคำขอดังกล่าว

แล้วส่วน Create ของ CRUD ล่ะ? เมื่อพูดถึงคำขอ HTTP นั้นเรียกว่าคำขอ POST เช่นเดียวกับที่คุณอาจ โพสต์ ข้อความบนแพลตฟอร์มโซเชียลมีเดีย คุณอาจ โพสต์ บันทึกใหม่ไปยังฐานข้อมูล

การอัปเดตของ CRUD ช่วยให้เราใช้ PUT หรือ PATCH Request เพื่ออัปเดตทรัพยากร PUT ของ HTTP จะสร้างระเบียนใหม่หรือจะอัปเดต/แทนที่ระเบียนเก่า

ดูรายละเอียดเพิ่มเติมเล็กน้อย แล้วเราจะไปที่ PATCH

โดยทั่วไป API จะทำงานโดยส่งคำขอ HTTP ไปยังเส้นทางที่ระบุใน URL สมมติว่าเรากำลังสร้าง API เพื่อพูดคุยกับฐานข้อมูลที่มีรายการหนังสือของผู้ใช้ จากนั้นเราอาจสามารถดูหนังสือเหล่านั้นได้ที่ URL .../books คำขอ POST ไปยัง .../books จะสร้างหนังสือเล่มใหม่พร้อมคุณสมบัติใดๆ ที่คุณกำหนด (คิดว่า id, ชื่อ, ISBN, ผู้แต่ง, ข้อมูลการเผยแพร่ ฯลฯ) ที่เส้นทาง .../books ไม่สำคัญว่าโครงสร้างข้อมูลพื้นฐานที่เก็บหนังสือทั้งหมดไว้ที่ .../books ในตอนนี้จะเป็นอย่างไร เราแค่สนใจว่า API จะเปิดเผยปลายทางนั้น (เข้าถึงผ่านเส้นทาง) เพื่อจัดการข้อมูล ประโยคก่อนหน้าเป็นกุญแจสำคัญ: คำขอ POST สร้าง หนังสือเล่มใหม่ที่เส้นทาง ...books/ ความแตกต่างระหว่าง PUT และ POST คือ PUT จะสร้างหนังสือเล่มใหม่ (เช่นเดียวกับ POST) หากไม่มีหนังสือดังกล่าวอยู่ หรือจะแทนที่หนังสือที่มีอยู่ หากมีหนังสืออยู่แล้วภายในโครงสร้างข้อมูลดังกล่าว

สมมติว่าหนังสือแต่ละเล่มมีคุณสมบัติดังต่อไปนี้: id, title, ISBN, author, hasRead (boolean)

จากนั้นหากต้องการเพิ่มหนังสือเล่มใหม่ ตามที่เห็นก่อนหน้านี้ เราจะขอ POST ไปที่ .../books หากเราต้องการอัปเดตหรือเปลี่ยนหนังสือโดยสมบูรณ์ เราจะส่งคำขอ PUT ไปที่ .../books/id โดยที่ id คือ ID ของหนังสือที่เราต้องการเปลี่ยน

แม้ว่า PUT จะแทนที่หนังสือที่มีอยู่โดยสมบูรณ์ แต่ PATCH จะอัปเดตบางอย่างที่เกี่ยวข้องกับหนังสือบางเล่ม บางทีอาจแก้ไขคุณสมบัติ hasRead boolean ที่เรากำหนดไว้ข้างต้น ดังนั้นเราจึงขอ PATCH เพื่อ …/books/id ส่งข้อมูลใหม่ไปพร้อม ๆ กัน

อาจเป็นเรื่องยากที่จะเห็นความหมายของสิ่งนี้ในขณะนี้ เนื่องจากจนถึงขณะนี้ เราได้สร้างทุกอย่างในทางทฤษฎีแล้ว แต่ยังไม่เห็นโค้ดที่จับต้องได้ใดๆ ที่ส่งคำขอ HTTP จริงๆ อย่างไรก็ตาม เราจะพูดถึงเรื่องนั้นในเร็วๆ นี้ ซึ่งครอบคลุม GET ในบทความนี้ และโฆษณาส่วนที่เหลือในบทความต่อๆ ไป

มีการดำเนินการ CRUD พื้นฐานครั้งสุดท้ายและเรียกว่า ลบ อย่างที่คุณคาดไว้ ชื่อของคำขอ HTTP ดังกล่าวคือ "DELETE" และทำงานเหมือนกับ PATCH โดยต้องระบุ ID ของหนังสือในเส้นทาง

ถึงตอนนี้ เราได้เรียนรู้แล้วว่าเส้นทางเป็น URL เฉพาะที่คุณสร้างคำขอ HTTP และปลายทางนั้นเป็นฟังก์ชันที่ API จัดเตรียมไว้ ซึ่งทำบางสิ่งกับข้อมูลที่เปิดเผย นั่นคือ จุดปลายเป็นฟังก์ชันภาษาโปรแกรมที่อยู่บนปลายอีกด้านของเส้นทาง และดำเนินการตามคำขอ HTTP ที่คุณระบุ เรายังได้เรียนรู้ว่ามีคำต่างๆ เช่น POST, GET, PUT, PATCH, DELETE และอื่นๆ (รู้จักกันในชื่อ HTTP verbs) ที่ระบุคำขอที่คุณส่งไปยัง API จริงๆ เช่นเดียวกับ JSON วิธีคำขอ HTTP เหล่านี้เป็นมาตรฐานอินเทอร์เน็ตตามที่กำหนดโดย Internet Engineering Task Force (IETF) โดยเฉพาะอย่างยิ่ง RFC 7231 ส่วนที่สี่: วิธีคำขอ และ RFC 5789 ส่วนที่สอง: วิธีแก้ไข โดยที่ RFC เป็นตัวย่อสำหรับ ขอความคิดเห็น

ดังนั้น เราอาจส่งคำขอ GET ไปยัง URL .../books/id โดยที่ ID ที่ส่งเข้ามานั้นเรียกว่าพารามิเตอร์ เราสามารถส่งคำขอ POST, PUT หรือ PATCH เพื่อ .../books เพื่อสร้างทรัพยากร หรือเพื่อ .../books/id เพื่อแก้ไข/แทนที่/อัปเดตทรัพยากร และเรายังสามารถส่งคำขอ DELETE เพื่อ .../books/id เพื่อลบหนังสือบางเล่ม

สามารถดูรายการ HTTP Request Methods ทั้งหมดได้ที่นี่

สิ่งสำคัญที่ควรทราบคือหลังจากส่งคำขอ HTTP เราจะได้รับการตอบกลับ การตอบสนองเฉพาะถูกกำหนดโดยวิธีที่เราสร้าง API แต่คุณควรได้รับรหัสสถานะเสมอ ก่อนหน้านี้ เรากล่าวว่าเมื่อเว็บเบราว์เซอร์ของคุณร้องขอ HTML จากเว็บเซิร์ฟเวอร์ เว็บจะตอบสนองด้วย "ตกลง" ซึ่งเรียกว่ารหัสสถานะ HTTP โดยเฉพาะอย่างยิ่ง HTTP 200 OK รหัสสถานะจะระบุวิธีการดำเนินการหรือการดำเนินการที่ระบุในปลายทาง (จำไว้ว่า นั่นคือฟังก์ชันของเราที่ทำงานทั้งหมด) เสร็จสมบูรณ์ เซิร์ฟเวอร์ส่งรหัสสถานะ HTTP กลับ และอาจมีหลายสิ่งที่คุณคุ้นเคย เช่น 404 Not Found (ไม่พบทรัพยากรหรือไฟล์ การทำเช่นนี้จะเหมือนกับการส่งคำขอ GET ไปที่ .../books/id ที่ไม่มี ID ดังกล่าว)

สามารถดูรายการรหัสสถานะ HTTP ทั้งหมดได้ที่นี่

MongoDB

MongoDB เป็นฐานข้อมูล NoSQL ที่ไม่สัมพันธ์กันซึ่งคล้ายกับฐานข้อมูลเรียลไทม์ของ Firebase คุณจะพูดคุยกับฐานข้อมูลผ่านแพ็คเกจ Node เช่น MongoDB Native Driver หรือ Mongoose

ใน MongoDB ข้อมูลจะถูกเก็บไว้ใน JSON ซึ่งค่อนข้างแตกต่างจากฐานข้อมูลเชิงสัมพันธ์ เช่น MySQL, PostgreSQL หรือ SQLite ทั้งสองเรียกว่าฐานข้อมูล โดยที่ตาราง SQL เรียกว่าคอลเลกชั่น แถวตาราง SQL เรียกว่าเอกสาร และคอลัมน์ตาราง SQL ที่เรียกว่าฟิลด์

เราจะใช้ฐานข้อมูล MongoDB ในบทความชุดต่อไปในชุดนี้ เมื่อเราสร้าง API สำหรับชั้นวางหนังสือเป็นครั้งแรก การดำเนินการ CRUD พื้นฐานที่ระบุไว้ข้างต้นสามารถทำได้บนฐานข้อมูล MongoDB

ขอแนะนำให้คุณอ่าน MongoDB Docs เพื่อเรียนรู้วิธีสร้างฐานข้อมูลสดบน Atlas Cluster และสร้าง CRUD Operations ด้วย MongoDB Native Driver ในบทความถัดไปของชุดนี้ เราจะเรียนรู้วิธีตั้งค่าฐานข้อมูลในเครื่องและฐานข้อมูลการผลิตบนระบบคลาวด์

การสร้างแอปพลิเคชันโหนดบรรทัดคำสั่ง

เมื่อสร้างแอปพลิเคชัน คุณจะเห็นผู้เขียนหลายคนทิ้งฐานรหัสทั้งหมดไว้ที่ตอนต้นของบทความ จากนั้นพยายามอธิบายแต่ละบรรทัดหลังจากนั้น ในข้อความนี้ ฉันจะใช้แนวทางที่แตกต่างออกไป ฉันจะอธิบายโค้ดของฉันทีละบรรทัด สร้างแอปต่อไป ฉันจะไม่กังวลเกี่ยวกับโมดูลาร์หรือประสิทธิภาพ ฉันจะไม่แบ่ง codebase ออกเป็นไฟล์แยกต่างหาก และฉันจะไม่ปฏิบัติตาม DRY Principle หรือพยายามทำให้โค้ดใช้ซ้ำได้ เมื่อเพียงแค่เรียนรู้ การทำสิ่งต่าง ๆ ให้เรียบง่ายที่สุดเท่าที่จะทำได้จะเป็นประโยชน์ และนั่นคือแนวทางที่ฉันจะดำเนินการที่นี่

ให้เรามีความชัดเจนเกี่ยวกับสิ่งที่เรากำลังสร้าง เราจะไม่กังวลกับการป้อนข้อมูลของผู้ใช้ และเราจะไม่ใช้ประโยชน์จากแพ็คเกจอย่าง Yargs เราจะไม่สร้าง API ของเราเองด้วย ซึ่งจะมาในบทความชุดหลังนี้เมื่อเราใช้ประโยชน์จาก Express Web Application Framework ฉันใช้วิธีนี้เพื่อไม่ให้ Node.js เชื่อมโยงด้วยพลังของ Express และ API เนื่องจากบทช่วยสอนส่วนใหญ่ทำ แต่ฉันจะให้วิธีหนึ่ง (จากหลายๆ วิธี) เพื่อโทรและรับข้อมูลจาก API ภายนอกซึ่งใช้ไลบรารี JavaScript ของบุคคลที่สาม API ที่เราจะเรียกคือ Weather API ซึ่งเราจะเข้าถึงจากโหนดและถ่ายโอนข้อมูลเอาต์พุตไปยังเทอร์มินัล อาจมีการจัดรูปแบบที่เรียกว่า "การพิมพ์ที่สวยงาม" ฉันจะอธิบายกระบวนการทั้งหมด รวมถึงวิธีตั้งค่า API และการรับคีย์ API ซึ่งขั้นตอนจะให้ผลลัพธ์ที่ถูกต้องในเดือนมกราคม 2019

เราจะใช้ OpenWeatherMap API สำหรับโครงการนี้ ดังนั้นเพื่อเริ่มต้น ให้ไปที่หน้าลงชื่อสมัครใช้ OpenWeatherMap และสร้างบัญชีด้วยแบบฟอร์ม เมื่อเข้าสู่ระบบแล้ว ให้ค้นหารายการเมนูคีย์ API บนหน้าแดชบอร์ด (อยู่ที่นี่) หากคุณเพิ่งสร้างบัญชี คุณจะต้องเลือกชื่อสำหรับคีย์ API ของคุณและกด "สร้าง" อาจใช้เวลาอย่างน้อย 2 ชั่วโมงเพื่อให้คีย์ API ใหม่ของคุณทำงานและเชื่อมโยงกับบัญชีของคุณ

ก่อนที่เราจะเริ่มสร้างแอปพลิเคชัน เราจะไปที่เอกสารประกอบ 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 ว่างเปล่า
เครื่องมือสำหรับนักพัฒนา Google Chrome ที่ว่างเปล่า (ตัวอย่างขนาดใหญ่)

หากต้องการตรวจสอบข้อมูลเครือข่ายจริง ให้โหลดหน้าเว็บซ้ำ และดูแท็บที่มีข้อมูลที่เป็นประโยชน์ คลิกลิงค์แรกตามภาพด้านล่าง

แท็บเครือข่ายเครื่องมือ Chrome Dev ที่มีประชากร
เครื่องมือสำหรับนักพัฒนา Google Chrome ที่มีข้อมูล (ตัวอย่างขนาดใหญ่)

เมื่อคุณคลิกลิงก์นั้น เราจะสามารถดูข้อมูลเฉพาะของ HTTP ได้ เช่น ส่วนหัว ส่วนหัวจะถูกส่งไปในการตอบกลับจาก API (ในบางกรณี คุณยังสามารถส่งส่วนหัวของคุณเองไปยัง API หรือคุณสามารถสร้างส่วนหัวที่กำหนดเองได้ (มักขึ้นต้นด้วย x- ) เพื่อส่งกลับเมื่อสร้าง API ของคุณเอง ) และมีเพียงข้อมูลเพิ่มเติมที่ไคลเอ็นต์หรือเซิร์ฟเวอร์อาจต้องการ

ในกรณีนี้ คุณจะเห็นว่าเราส่งคำขอ HTTP GET ไปยัง API และตอบกลับด้วยสถานะ HTTP 200 ตกลง คุณยังดูได้ว่าข้อมูลที่ส่งกลับอยู่ใน JSON ตามที่แสดงไว้ในส่วน "ส่วนหัวการตอบสนอง"

Google Dev Tools ที่แสดงภาพส่วนหัวจากการตอบกลับ API
ส่วนหัวในการตอบกลับจาก API (ตัวอย่างขนาดใหญ่)

หากคุณกดแท็บแสดงตัวอย่าง คุณสามารถดู JSON เป็นออบเจกต์ JavaScript ได้ เวอร์ชันข้อความที่คุณเห็นในเบราว์เซอร์ของคุณเป็นสตริง เนื่องจาก JSON จะส่งและรับผ่านเว็บเป็นสตริงเสมอ นั่นเป็นเหตุผลที่เราต้องแยกวิเคราะห์ JSON ในโค้ดของเรา เพื่อให้มันอยู่ในรูปแบบที่อ่านง่ายขึ้น ในกรณีนี้ (และแทบทุกกรณี) — JavaScript Object

คุณยังสามารถใช้ส่วนขยาย 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.js

ตั้งแต่ตอนนี้ โค้ดต่อไปนี้ทั้งหมดจะอยู่ภายในไฟล์ 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(...) ส่งคืน 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)

ตอนนี้ เราบอกว่าเว็บเซิร์ฟเวอร์จัดการกับ JSON เป็นสตริงเสมอ และนั่นก็เป็นความจริง อย่างไรก็ตาม คุณอาจสังเกตเห็นว่า response.data เป็นวัตถุอยู่แล้ว (เห็นได้จากการรัน console.log(typeof response.data) ) — เราไม่ต้องแยกวิเคราะห์ด้วย JSON.parse() นั่นเป็นเพราะว่า Axios ได้ดูแลเรื่องนี้ให้เราเบื้องหลังแล้ว

เอาต์พุตในเทอร์มินัลจากการรัน console.log(response.data) สามารถฟอร์แมต — “pretty-printed” — โดยการรัน 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));

อุณหภูมิที่เราได้กลับมาจริง ๆ แล้วเป็นเคลวิน ซึ่งเป็นมาตราส่วนอุณหภูมิที่มักใช้ในฟิสิกส์ เคมี และอุณหพลศาสตร์ เนื่องจากมีจุด "ศูนย์สัมบูรณ์" ซึ่งเป็นอุณหภูมิที่ความร้อนภายในทั้งหมดเคลื่อนที่ อนุภาคหยุด เราแค่ต้องแปลงเป็นฟาเรนไฮต์หรือเซลเซียสด้วยสูตรด้านล่าง:

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 แค่ดูดี — คล้ายกับเมื่อทำงานกับ JSX ใน React แบ็กสแลชหยุดไม่ให้สตริงเทมเพลตจัดรูปแบบบรรทัดใหม่ และวิธีการ replace() ต้นแบบสตริงจะกำจัดช่องว่างโดยใช้นิพจน์ทั่วไป (RegEx) วิธีการสร้างต้นแบบตัวเลข toFixed() จะปัดเศษทศนิยมให้เป็นจำนวนเฉพาะ ในกรณีนี้คือ 2 ตำแหน่ง

ด้วยเหตุนี้ 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));

บทสรุป

เราได้เรียนรู้มากมายเกี่ยวกับวิธีการทำงานของโหนดในบทความนี้ ตั้งแต่ความแตกต่างระหว่างคำขอแบบซิงโครนัสและแบบอะซิงโครนัส ไปจนถึงฟังก์ชันการโทรกลับ ไปจนถึงฟีเจอร์ ES6 ใหม่ เหตุการณ์ ตัวจัดการแพ็คเกจ APIs JSON และ HyperText Transfer Protocol ฐานข้อมูลที่ไม่ใช่เชิงสัมพันธ์ และเราได้สร้างแอปพลิเคชันบรรทัดคำสั่งของเราเองโดยใช้ความรู้ที่ค้นพบใหม่เกือบทั้งหมด

ในบทความต่อๆ ไปในชุดนี้ เราจะเจาะลึกถึง Call Stack, Event Loop และ Node APIs เราจะพูดถึง Cross-Origin Resource Sharing (CORS) และเราจะสร้างฉบับเต็ม Stack Bookshelf API ที่ใช้ฐานข้อมูล จุดปลาย การตรวจสอบผู้ใช้ โทเค็น การแสดงเทมเพลตฝั่งเซิร์ฟเวอร์ และอื่นๆ

จากที่นี่ ให้เริ่มสร้างแอปพลิเคชัน Node ของคุณเอง อ่านเอกสารประกอบของ Node ออกไปค้นหา API หรือ Node Module ที่น่าสนใจและนำไปใช้ด้วยตนเอง โลกคือหอยนางรมของคุณและคุณสามารถเข้าถึงเครือข่ายความรู้ที่ใหญ่ที่สุดในโลกได้เพียงปลายนิ้วสัมผัส - อินเทอร์เน็ต ใช้มันเพื่อประโยชน์ของคุณ

อ่านเพิ่มเติม เกี่ยวกับ SmashingMag:

  • ทำความเข้าใจและใช้งาน REST API
  • ฟีเจอร์ JavaScript ใหม่ที่จะเปลี่ยนวิธีการเขียน Regex . ของคุณ
  • การรักษา Node.js ให้รวดเร็ว: เครื่องมือ เทคนิค และเคล็ดลับสำหรับการสร้างเซิร์ฟเวอร์ Node.js ประสิทธิภาพสูง
  • สร้าง AI Chatbot อย่างง่ายด้วย Web Speech API และ Node.js